Adding working rough draft of node sample application

This commit is contained in:
Jim Cowart 2012-04-18 01:05:32 -04:00
parent 57410d175a
commit 30a3995f27
815 changed files with 242567 additions and 693 deletions

View file

@ -1,8 +1,9 @@
build-browser-diags.json
build-browser.json
build-node-diags.json
build-node.json
build-all.sh
nodetesthost.js
example/
node_modules/
spec/
src/
ext/

View file

@ -5,10 +5,8 @@ anvil -b build-browser-diags.json
mv ./lib/standard/postal.amd.js ./lib/amd/postal.js
mv ./lib/standard/postal.amd.min.js ./lib/amd/postal.min.js
mv ./lib/standard/postal.amd.min.gz.js ./lib/amd/postal.min.gz.js
mv ./lib/standard/postal.diagnostics.amd.js ./lib/amd/postal.diagnostics.js
mv ./lib/standard/postal.diagnostics.amd.min.js ./lib/amd/postal.diagnostics.min.js
mv ./lib/standard/postal.diagnostics.amd.min.gz.js ./lib/amd/postal.diagnostics.min.gz.js
mv ./lib/standard/postal.diagnostics.node.js ./lib/node/postal.diagnostics.js
mv ./lib/standard/postal.node.js ./lib/node/postal.js
@ -17,10 +15,12 @@ rm ./lib/standard/postal.node*
mv ./lib/standard/postal.standard.js ./lib/standard/postal.js
mv ./lib/standard/postal.standard.min.js ./lib/standard/postal.min.js
mv ./lib/standard/postal.standard.min.gz.js ./lib/standard/postal.min.gz.js
mv ./lib/standard/postal.diagnostics.standard.js ./lib/standard/postal.diagnostics.js
mv ./lib/standard/postal.diagnostics.standard.min.js ./lib/standard/postal.diagnostics.min.js
mv ./lib/standard/postal.diagnostics.standard.min.gz.js ./lib/standard/postal.diagnostics.min.gz.js
cp ./lib/standard/postal.* ./example/standard/js
cp ./lib/amd/postal.* ./example/amd/js/libs/postal
cp ./lib/amd/postal.* ./example/amd/js/libs/postal
cp ./lib/amd/postal.js ./example/node/client/js/lib
cp ./lib/amd/postal.diagnostics.js ./example/node/client/js/lib
cp ./lib/node/postal.diagnostics.js ./example/node/messaging
cp ./lib/node/postal.js ./example/node/messaging

View file

@ -3,6 +3,5 @@
"output": "lib/standard",
"lint": {},
"uglify": {},
"gzip": {},
"extensions": { "uglify": "min", "gzip": "gz" }
"extensions": { "uglify": "min" }
}

View file

@ -3,6 +3,9 @@
"output": "lib/standard",
"lint": {},
"uglify": {},
"gzip": {},
"extensions": { "uglify": "min", "gzip": "gz" }
"extensions": { "uglify": "min" },
"hosts": {
"/" : "./"
},
"port" : 8080
}

View file

@ -1,5 +0,0 @@
{
"source": "src/diags",
"output": "lib/node",
"lint": {}
}

View file

@ -1,5 +0,0 @@
{
"source": "src/main",
"output": "lib/node",
"lint": {}
}

View file

@ -51,7 +51,7 @@ ChannelDefinition.prototype = {
var envelope = {
channel: this.channel,
topic: this._topic,
data: obj
data: obj || {}
};
// If this is an envelope....
if( obj.topic && obj.data ) {
@ -60,6 +60,7 @@ ChannelDefinition.prototype = {
}
envelope.timeStamp = new Date();
postal.configuration.bus.publish(envelope);
return envelope;
},
topic: function(topic) {
@ -241,7 +242,7 @@ var localBus = {
_.each(this.subscriptions[envelope.channel], function(topic) {
_.each(topic, function(subDef){
if(postal.configuration.resolver.compare(subDef.topic, envelope.topic)) {
if(_.all(subDef.constraints, function(constraint) { return constraint(envelope.data); })) {
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();
@ -303,21 +304,53 @@ var localBus = {
};
var publishPicker = {
"1" : function(envelope) {
if(!envelope) {
throw new Error("publishing from the 'global' postal.publish call requires a valid envelope.");
"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;
}
envelope.channel = envelope.channel || DEFAULT_CHANNEL;
envelope.timeStamp = new Date();
postal.configuration.bus.publish(envelope);
},
"2" : function(topic, data) {
postal.configuration.bus.publish({ channel: DEFAULT_CHANNEL, topic: topic, timeStamp: new Date(), data: data });
},
"3" : function(channel, topic, data) {
postal.configuration.bus.publish({ channel: channel, topic: topic, timeStamp: new Date(), data: data });
}
};
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 || "LocalChannel" ]( 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 || "LocalChannel" ]( channel, topic );
},
"3" : function( channel, topic, options ) {
return new postal.channelTypes[ options.type || "LocalChannel" ]( channel, topic );
}
};
// save some setup time, albeit tiny
localBus.subscriptions[SYSTEM_CHANNEL] = {};
@ -325,24 +358,22 @@ localBus.subscriptions[SYSTEM_CHANNEL] = {};
var postal = {
configuration: {
bus: localBus,
resolver: bindingsResolver
resolver: bindingsResolver,
DEFAULT_CHANNEL: DEFAULT_CHANNEL,
DEFAULT_PRIORITY: DEFAULT_PRIORITY,
DEFAULT_DISPOSEAFTER: DEFAULT_DISPOSEAFTER,
SYSTEM_CHANNEL: SYSTEM_CHANNEL
},
channelTypes: {
LocalChannel: ChannelDefinition
},
channel: function() {
var len = arguments.length,
channel = arguments[0],
tpc = arguments[1];
if(len === 1) {
if(Object.prototype.toString.call(channel) === "[object String]") {
channel = DEFAULT_CHANNEL;
tpc = arguments[0];
}
else {
channel = arguments[0].channel || DEFAULT_CHANNEL;
tpc = arguments[0].topic;
}
var len = arguments.length;
if(channelPicker[len]) {
return channelPicker[len].apply(this, arguments);
}
return new ChannelDefinition(channel, tpc);
},
subscribe: function(options) {
@ -355,7 +386,7 @@ var postal = {
publish: function() {
var len = arguments.length;
if(publishPicker[len]) {
publishPicker[len].apply(this, arguments);
return publishPicker[len].apply(this, arguments);
}
},

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,51 @@
body {
font-family: Tahoma, Arial;
font-size: 9pt;
}
fieldset {
width: 285px;
float: left;
}
.images {
height: 48px;
width: 48px;
border: .5pt solid black;
}
.scrollableDiv {
overflow-y: auto;
overflow-x: hidden;
height: 570px;
}
.top-bar {
position: relative;
left: 0px;
top: 0px;
margin-bottom: 25px;
}
.nav-menu {
float: left;
margin: 0px;
margin-right: 30px;
}
.nav-menu li {
display: inline-block;
margin-right: 10px;
}
.search {
float: left;
}
.search input[type='text'] {
margin-left: 5px;
}
#stats {
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<base href="/">
<title>Twitter Hash Tag Stats Demo</title>
<link rel="stylesheet" type="text/css" href="css/style.css">
<script src="/socket.io/socket.io.js"></script>
<script data-main="js/main" src="js/lib/require-jquery.js"></script>
</head>
<body>
</body>
</html>

View file

@ -0,0 +1,56 @@
define([
'jquery',
'backbone',
'bus',
'infrastructure/router',
'infrastructure/view-manager',
'views/container',
'views/menu',
'views/tweet-count',
'views/mention-count',
'views/mentioner-count',
'views/hash-tag-count',
'views/profanity-percentage'
], function( $, Backbone, bus, Router, ViewManager, ContainerView, MenuView, TweetCountView, MentionCountView,
MentionerCountView, HashTagCountView, ProfanityPercentage ) {
var app = {
bus: bus,
router: new Router()
};
postal.configuration.getSessionIdentifier(
function( id ) {
console.log("SessionID: " + id);
}
);
// Set up UI concerns
app.viewManager = new ViewManager();
app.bus.viewManager.subscribe( "ui.show", function( data, env ) {
app.viewManager.UI[ data.name ].activate( data.context );
});
app.viewManager.registerViews([
{ name: "container", ctor: ContainerView },
{ name: "menu", ctor: MenuView },
{ name: "tweetCount", ctor: TweetCountView },
{ name: "mentionCount", ctor: MentionCountView },
{ name: "mentionerCount", ctor: MentionerCountView },
{ name: "hashTagCount", ctor: HashTagCountView },
{ name: "profanityPercentage", ctor: ProfanityPercentage }
]);
app.viewManager.defineUIs([
{ name: "homeUI", dependencies: [ "container", "menu", "tweetCount", "mentionCount", "mentionerCount", "hashTagCount", "profanityPercentage" ] },
{ name: "statSelectionUI", dependencies: [ "container", "menu" ] },
{ name: "wireTapLogUI", dependencies: [ "menu" ], options: { noHide: true } }
]);
$(function() {
Backbone.history.start({
pushState: true,
root: $( "base" ).attr( "href" )
});
});
return app;
});

View file

@ -0,0 +1,11 @@
define([
'postal'
], function ( postal ) {
return {
router : postal.channel( "router", "*" ),
viewManager : postal.channel( "viewmanager", "*" ),
data : postal.channel( "data", "*" ),
app : postal.channel( "statsApp", "*", { type: "websocket" } ),
stats : postal.channel( "stats", "*", { type: "websocket" } )
}
});

View file

@ -0,0 +1,316 @@
/*
postal.socket
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
*/
(function( root, doc, factory ) {
if ( typeof define === "function" && define.amd ) {
// AMD. Register as an anonymous module.
define( [ "underscore", "machina", "postal" ], function( _, machina, postal ) {
return factory( _, machina, postal, root, doc );
});
} else {
// Browser globals
factory( root._, root.machina, root.postal, root, doc );
}
}(this, document, function( _, machina, postal, global, document, undefined ) {
var sessionId = undefined;
// Default implementation passes back undefined, which means
// the socket.io socket sessionid will be used....
postal.configuration.getSessionIdentifier = function( callback ) {
callback( sessionId );
};
postal.configuration.setSessionIdentifier = function( value ) {
sessionId = value;
};
postal.configuration.lastSessionId = null;
postal.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;
};
/*
adding a socket namespace to postal
which provides the following members:
1.) config - provides values used to manage the socket connection
2.) goOffline() - tells the manager to close the connection intentionally
3.) goOnline() - tells the manager to try to connect.
4.) manifest - an array of objects describing the subscriptions that have been
set up on the remote end.
5.) publish() - takes a valid postal envelope and pushes it through the socket
to be published to the server instance of postal.
6.) socketMgr - the FSM managing the socket connection
7.) socketNamespace - exposed currently for debugging only
8.) subscribe() - passes an object through the socket to the server
which contains data necessary to set up a remote subscription. The
options passed to the socket would look similar to this:
{
"channel":"SomeChannel",
"topic":"my.topic",
"correlationId":"2073383865318591267"
}
The "correlationId" is used on the remote side to apply a constraint to
the subscription, enabling just this specific client to be targeted on
and otherwise public channel.
9.) unsubscribe() - passes an object through the socket to the server
which contains data necessary to remove a remote subscription. The options
passed would look similar to the example above in #8.
*/
postal.connections = postal.connections || {};
var postalSocket = postal.connections.socket = (function(){
var socketNamespace,
fsm = new machina.Fsm({
retryFn: undefined,
wireUpSocketEvents: function() {
var self = this;
_.each([ "connect", "connecting", "connect_failed", "disconnect", "reconnect", "reconnect_failed",
"reconnecting", "postal.socket.remote", "postal.socket.identified", "postal.socket.migration" ],
function( evnt ) {
socketNamespace.on( evnt, function( data ) {
self.handle( evnt, data );
});
});
},
states: {
uninitialized: {
tryConnect: function() {
this.transition("initializing");
}
},
initializing: {
_onEnter: function() {
socketNamespace = io.connect(postalSocket.config.url, { "auto connect": false });
this.wireUpSocketEvents();
this.transition("probing");
},
socketTransmit: function() {
this.deferUntilTransition("online");
}
},
probing: {
_onEnter: function() {
clearTimeout(this.retryFn);
if(!socketNamespace.socket.connecting && !socketNamespace.socket.reconnecting) {
socketNamespace.socket.connect();
}
},
connect: function(){
this.transition("identifying");
},
connect_failed: function() {
this.transition("disconnected");
},
maxAttempts: function() {
this.transition("offline");
},
"postal.socket.remote" : function() {
this.deferUntilTransition("online");
},
reconnect: function(){
this.transition("identifying");
},
reconnect_failed: function() {
this.transition("disconnected");
},
socketTransmit: function() {
this.deferUntilTransition("online");
}
},
identifying: {
_onEnter: function() {
var self = this;
self.retryFn = setTimeout(function() {
self.handle( "timeout.identifying" );
},postalSocket.config.reconnectInterval );
postal.configuration.getSessionIdentifier( function( id ) {
if( !id ) {
postal.configuration.setSessionIdentifier( socketNamespace.socket.sessionid );
}
self.handle( "client.identifier", { sessionId: id || socketNamespace.socket.sessionid } );
});
},
"client.identifier" : function( data ) {
clearTimeout( this.retryFn );
var lastSessionId = postal.configuration.lastSessionId;
postal.configuration.lastSessionId = data.sessionId;
socketNamespace.emit( "postal.clientId", { sessionId: postal.configuration.lastSessionId, lastSessionId: lastSessionId } );
},
connect_failed: function() {
this.transition("disconnected");
},
disconnect: function() {
this.transition("probing");
},
"postal.socket.identified" : function( data ) {
this.transition("online");
},
"postal.socket.migration" : function() {
_.each(postal.socket.manifest, function( sub ) {
fsm.handle( "socketTransmit", "postal.subscribe", sub );
});
socketNamespace.emit( "postal.migrationComplete", {} );
},
"postal.socket.remote" : function() {
this.deferUntilTransition("online");
},
reconnect_failed: function() {
this.transition("disconnected");
},
socketTransmit: function( evntName, envelope ) {
if( evntName === "postal.subscribe" ){
// we risk mutating the message here, so extend
// and add the correlationId to the extended copy
var socketEnv = _.extend( {}, envelope );
socketEnv.correlationId = postal.configuration.lastSessionId;
socketNamespace.emit(evntName, socketEnv);
}
else {
this.deferUntilTransition("online");
}
},
"timeout.identifying" : function() {
this.transition("probing");
}
},
online: {
disconnect: function() {
this.transition("probing");
},
goOffline: function() {
this.transition("offline");
},
"postal.socket.remote" : function( envelope ) {
postal.publish( envelope );
},
socketTransmit: function( evntName, envelope ) {
// we risk mutating the message here, so extend
// and add the correlationId to the extended copy
var socketEnv = _.extend( {}, envelope );
socketEnv.correlationId = postal.configuration.lastSessionId;
socketNamespace.emit(evntName, socketEnv);
}
},
offline: {
_onEnter: function() {
socketNamespace.socket.disconnect();
},
socketTransmit: function() {
this.deferUntilTransition("online");
},
"tryConnect": function() {
this.transition("probing");
}
},
disconnected: {
_onEnter: function() {
var self = this;
self.retryFn = setTimeout(function() {
self.transition("probing");
},postalSocket.config.reconnectInterval);
},
connecting: function() {
this.transition("probing");
},
reconnecting: function() {
this.transition("probing");
},
socketTransmit: function() {
this.deferUntilTransition("online");
}
}
}
});
return {
config : {
url: window.location.origin,
reconnectInterval: 4000
},
goOffline: function() {
fsm.handle( "goOffline" );
},
goOnline: function() {
fsm.handle( "tryConnect" );
},
manifest: [],
publish: function( envelope ) {
fsm.handle( "socketTransmit", "postal.publish", envelope );
},
subscribe: function( options ) {
options.channel = options.channel || postal.configuration.DEFAULT_CHANNEL;
options.topic = options.topic || "*";
if( !_.any( this.manifest, function( item ){
return item.channel === options.channel && item.topic === options.topic;
})) {
this.manifest.push( options );
fsm.handle( "socketTransmit", "postal.subscribe", options );
}
},
socketMgr: fsm,
socketNamespace: socketNamespace,
unsubscribe: function( options ) {
options.channel = options.channel || postal.configuration.DEFAULT_CHANNEL;
options.topic = options.topic || "*";
if( !postal.getSubscribersFor( options.channel, options.topic ).length ) {
fsm.handle( "socketTransmit", "postal.unsubscribe", options);
}
}
}
})();
postal.connections.socket.goOnline();
var SocketChannel = postal.channelTypes.websocket = function( channelName, defaultTopic ) {
var channel = postal.channel( channelName, defaultTopic ),
localSubscribe = channel.subscribe,
localPublish = channel.publish,
localTopic = channel.topic;
channel.publish = function() {
postalSocket.publish( localPublish.apply( channel, arguments) );
};
channel.subscribe = function() {
var sub = localSubscribe.apply( channel, arguments),
origUnsubscribe;
origUnsubscribe = sub.unsubscribe;
sub.unsubscribe = function() {
origUnsubscribe.call(sub);
postalSocket.unsubscribe({ channel: sub.channel, topic: sub.topic });
};
postalSocket.subscribe({ channel: sub.channel, topic: sub.topic });
return sub;
};
channel.topic = function( topic ) {
if(topic === channel._topic) {
return this;
}
return new SocketChannel(this.channel, topic);
};
return channel;
};
}));

View file

@ -0,0 +1,52 @@
define([
'jquery',
'backbone',
'bus'
], function( $, Backbone, bus ){
return Backbone.Router.extend({
routes: {
"" : "home",
"select" : "select",
"wiretap" : "wiretap",
"*anything" : "redirect"
},
initialize: function() {
var self = this;
_.bindAll( self );
$( document ).delegate( "a.ps-nav", "click", function( e ){
e.preventDefault();
self.navigate( $( this ).attr( 'href' ), { trigger: true });
});
bus.router.publish( "initialized" );
},
activateUI: function( uiName, context ) {
bus.viewManager.publish({
topic: "ui.show",
data: {
name: uiName,
context: context
}
});
},
home: function() {
this.activateUI( "homeUI" );
},
select: function() {
this.activateUI( "statSelectionUI" );
},
wiretap: function() {
this.activateUI( "wireTapLogUI" );
},
redirect: function() {
this.navigate( "/", { trigger: true });
}
});
});

View file

@ -0,0 +1,121 @@
define([
'underscore',
'bus'
], function( _, bus ){
var ViewManager = function() {
this.views = {}; // holds the views that are registered with the manager
this.UI = {}; // holds the UI configurations that are defined
this.priorContext = undefined; // holds the name of the last UI configuration
};
//----------------------------------------------------------------------------
// registerView - registers a view with the manager, under the name provided.
// The handle to the constructor function (second arg) is used to create an
// instance of the view the first time getInstance is called. The rendered
// and visible booleans are used by the ViewManager to track state of the view.
// The getInstance call can take an options object. If options.forceNew = true,
// then the ViewManager will create a new instance of the view. If options.args
// exists, it will be passed into the constructor function of the view.
//----------------------------------------------------------------------------
ViewManager.prototype.registerView = function(name, viewCtor) {
this.views[name] = {
rendered: false,
visible: false,
getInstance: (function(){
var _instance;
return function(options){
var _options = options || {};
if(!_instance || _options.forceNew) {
_instance = new viewCtor(_options.args || {});
}
return _instance;
}
})()
}
};
ViewManager.prototype.registerViews = function(views) {
_.each( views, function( view ){
this.registerView( view.name, view.ctor );
}, this );
};
//----------------------------------------------------------------------------
// defineUI - defines a UI configuration, which is effectively a named group
// of views that need to be stood up, in order. The first argument is the UI
// name, second arg is the array of view names (in the order they need to be
// instantiated/rendered/shown)
//----------------------------------------------------------------------------
ViewManager.prototype.defineUI = function( name, dependencies, options ) {
var self = this;
self.UI[ name ] = {
options: options || {},
dependencies: dependencies,
activate: function( data ) {
data = data || {};
data.priorContext = self.priorContext;
data.targetContext = name;
if( !this.options.noHide ) {
// hide anything visible that's not in the dependencies for this UI configuration
var shouldHide = _.reduce( self.views, function( memo, val, key ){
if( val.visible && !_.include( this.dependencies, key ) ){
memo.push( key );
}
return memo;
}, [], this );
_.each( shouldHide, function( viewName ){
var instance = self.views[ viewName ].getInstance();
if( instance.hide ) {
instance.hide();
}
self.views[ viewName ].visible = false;
});
}
// set up, render & show the dependencies for this UI configuration
_.each(this.dependencies, function(viewName){
var instance = self.views[viewName].getInstance(data);
if(!self.views[viewName].rendered) {
instance.render(data);
self.views[viewName].rendered = true;
}
if(!self.views[viewName].visible) {
if(instance.show) {
instance.show(data);
}
self.views[viewName].visible = true;
}
if(instance.update) {
instance.update(data);
}
});
self.priorContext = name;
}
};
};
ViewManager.prototype.defineUIs = function( uis ) {
_.each( uis, function( ui ){
this.defineUI( ui.name, ui.dependencies, ui.options );
}, this );
};
ViewManager.prototype.addViewToUI = function( uiName, viewName, viewCtor ) {
var uis = _.isArray( uiName ) ? uiName : [ uiName ];
if( !this.views[ viewName ] ) {
this.registerView( viewName, viewCtor );
}
_.each( uis, function( ui ) {
if( this.UI[ ui ] ) {
this.UI[ ui ].dependencies.push( viewName );
}
}, this );
};
return ViewManager;
});

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,39 @@
// Backbone.js 0.9.2
// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Backbone may be freely distributed under the MIT license.
// For all details and documentation:
// http://backbonejs.org
(function(h,g){typeof exports!=="undefined"?g(h,exports,require("underscore")):typeof define==="function"&&define.amd?define(["underscore","jquery","exports"],function(f,i,p){h.Backbone=g(h,p,f,i)}):h.Backbone=g(h,{},h._,h.jQuery||h.Zepto||h.ender)})(this,function(h,g,f,i){var p=h.Backbone,y=Array.prototype.slice,z=Array.prototype.splice;g.VERSION="0.9.2";g.setDomLibrary=function(a){i=a};g.noConflict=function(){h.Backbone=p;return g};g.emulateHTTP=false;g.emulateJSON=false;var q=/\s+/,l=g.Events=
{on:function(a,b,c){var d,e,f,g,j;if(!b)return this;a=a.split(q);for(d=this._callbacks||(this._callbacks={});e=a.shift();)f=(j=d[e])?j.tail:{},f.next=g={},f.context=c,f.callback=b,d[e]={tail:g,next:j?j.next:f};return this},off:function(a,b,c){var d,e,k,g,j,h;if(e=this._callbacks){if(!a&&!b&&!c)return delete this._callbacks,this;for(a=a?a.split(q):f.keys(e);d=a.shift();)if(k=e[d],delete e[d],k&&(b||c))for(g=k.tail;(k=k.next)!==g;)if(j=k.callback,h=k.context,b&&j!==b||c&&h!==c)this.on(d,j,h);return this}},
trigger:function(a){var b,c,d,e,f,g;if(!(d=this._callbacks))return this;f=d.all;a=a.split(q);for(g=y.call(arguments,1);b=a.shift();){if(c=d[b])for(e=c.tail;(c=c.next)!==e;)c.callback.apply(c.context||this,g);if(c=f){e=c.tail;for(b=[b].concat(g);(c=c.next)!==e;)c.callback.apply(c.context||this,b)}}return this}};l.bind=l.on;l.unbind=l.off;var o=g.Model=function(a,b){var c;a||(a={});b&&b.parse&&(a=this.parse(a));if(c=n(this,"defaults"))a=f.extend({},c,a);if(b&&b.collection)this.collection=b.collection;
this.attributes={};this._escapedAttributes={};this.cid=f.uniqueId("c");this.changed={};this._silent={};this._pending={};this.set(a,{silent:true});this.changed={};this._silent={};this._pending={};this._previousAttributes=f.clone(this.attributes);this.initialize.apply(this,arguments)};f.extend(o.prototype,l,{changed:null,_silent:null,_pending:null,idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},get:function(a){return this.attributes[a]},escape:function(a){var b;
if(b=this._escapedAttributes[a])return b;b=this.get(a);return this._escapedAttributes[a]=f.escape(b==null?"":""+b)},has:function(a){return this.get(a)!=null},set:function(a,b,c){var d,e;f.isObject(a)||a==null?(d=a,c=b):(d={},d[a]=b);c||(c={});if(!d)return this;if(d instanceof o)d=d.attributes;if(c.unset)for(e in d)d[e]=void 0;if(!this._validate(d,c))return false;if(this.idAttribute in d)this.id=d[this.idAttribute];var b=c.changes={},g=this.attributes,h=this._escapedAttributes,j=this._previousAttributes||
{};for(e in d){a=d[e];if(!f.isEqual(g[e],a)||c.unset&&f.has(g,e))delete h[e],(c.silent?this._silent:b)[e]=true;c.unset?delete g[e]:g[e]=a;!f.isEqual(j[e],a)||f.has(g,e)!=f.has(j,e)?(this.changed[e]=a,c.silent||(this._pending[e]=true)):(delete this.changed[e],delete this._pending[e])}c.silent||this.change(c);return this},unset:function(a,b){(b||(b={})).unset=true;return this.set(a,null,b)},clear:function(a){(a||(a={})).unset=true;return this.set(f.clone(this.attributes),a)},fetch:function(a){var a=
a?f.clone(a):{},b=this,c=a.success;a.success=function(d,e,f){if(!b.set(b.parse(d,f),a))return false;c&&c(b,d)};a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},save:function(a,b,c){var d,e;f.isObject(a)||a==null?(d=a,c=b):(d={},d[a]=b);c=c?f.clone(c):{};if(c.wait){if(!this._validate(d,c))return false;e=f.clone(this.attributes)}a=f.extend({},c,{silent:true});if(d&&!this.set(d,c.wait?a:c))return false;var k=this,h=c.success;c.success=function(a,b,e){b=k.parse(a,e);
c.wait&&(delete c.wait,b=f.extend(d||{},b));if(!k.set(b,c))return false;h?h(k,a):k.trigger("sync",k,a,c)};c.error=g.wrapError(c.error,k,c);b=this.isNew()?"create":"update";b=(this.sync||g.sync).call(this,b,this,c);c.wait&&this.set(e,a);return b},destroy:function(a){var a=a?f.clone(a):{},b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};if(this.isNew())return d(),false;a.success=function(e){a.wait&&d();c?c(b,e):b.trigger("sync",b,e,a)};a.error=g.wrapError(a.error,b,a);var e=(this.sync||
g.sync).call(this,"delete",this,a);a.wait||d();return e},url:function(){var a=n(this,"urlRoot")||n(this.collection,"url")||t();return this.isNew()?a:a+(a.charAt(a.length-1)=="/"?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return this.id==null},change:function(a){a||(a={});var b=this._changing;this._changing=true;for(var c in this._silent)this._pending[c]=true;var d=f.extend({},a.changes,this._silent);
this._silent={};for(c in d)this.trigger("change:"+c,this,this.get(c),a);if(b)return this;for(;!f.isEmpty(this._pending);){this._pending={};this.trigger("change",this,a);for(c in this.changed)!this._pending[c]&&!this._silent[c]&&delete this.changed[c];this._previousAttributes=f.clone(this.attributes)}this._changing=false;return this},hasChanged:function(a){return!arguments.length?!f.isEmpty(this.changed):f.has(this.changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this.changed):
false;var b,c=false,d=this._previousAttributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return!arguments.length||!this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},isValid:function(){return!this.validate(this.attributes)},_validate:function(a,b){if(b.silent||!this.validate)return true;var a=f.extend({},this.attributes,a),c=this.validate(a,b);if(!c)return true;b&&b.error?
b.error(this,c,b):this.trigger("error",this,c,b);return false}});var r=g.Collection=function(a,b){b||(b={});if(b.model)this.model=b.model;if(b.comparator)this.comparator=b.comparator;this._reset();this.initialize.apply(this,arguments);a&&this.reset(a,{silent:true,parse:b.parse})};f.extend(r.prototype,l,{model:o,initialize:function(){},toJSON:function(a){return this.map(function(b){return b.toJSON(a)})},add:function(a,b){var c,d,e,g,h,j={},i={},l=[];b||(b={});a=f.isArray(a)?a.slice():[a];for(c=0,d=
a.length;c<d;c++){if(!(e=a[c]=this._prepareModel(a[c],b)))throw Error("Can't add an invalid model to a collection");g=e.cid;h=e.id;j[g]||this._byCid[g]||h!=null&&(i[h]||this._byId[h])?l.push(c):j[g]=i[h]=e}for(c=l.length;c--;)a.splice(l[c],1);for(c=0,d=a.length;c<d;c++)(e=a[c]).on("all",this._onModelEvent,this),this._byCid[e.cid]=e,e.id!=null&&(this._byId[e.id]=e);this.length+=d;z.apply(this.models,[b.at!=null?b.at:this.models.length,0].concat(a));this.comparator&&this.sort({silent:true});if(b.silent)return this;
for(c=0,d=this.models.length;c<d;c++)if(j[(e=this.models[c]).cid])b.index=c,e.trigger("add",e,this,b);return this},remove:function(a,b){var c,d,e,g;b||(b={});a=f.isArray(a)?a.slice():[a];for(c=0,d=a.length;c<d;c++)if(g=this.getByCid(a[c])||this.get(a[c])){delete this._byId[g.id];delete this._byCid[g.cid];e=this.indexOf(g);this.models.splice(e,1);this.length--;if(!b.silent)b.index=e,g.trigger("remove",g,this,b);this._removeReference(g)}return this},push:function(a,b){a=this._prepareModel(a,b);this.add(a,
b);return a},pop:function(a){var b=this.at(this.length-1);this.remove(b,a);return b},unshift:function(a,b){a=this._prepareModel(a,b);this.add(a,f.extend({at:0},b));return a},shift:function(a){var b=this.at(0);this.remove(b,a);return b},get:function(a){return a==null?void 0:this._byId[a.id!=null?a.id:a]},getByCid:function(a){return a&&this._byCid[a.cid||a]},at:function(a){return this.models[a]},where:function(a){return f.isEmpty(a)?[]:this.filter(function(b){for(var c in a)if(a[c]!==b.get(c))return false;
return true})},sort:function(a){a||(a={});if(!this.comparator)throw Error("Cannot sort a set without a comparator");var b=f.bind(this.comparator,this);this.comparator.length==1?this.models=this.sortBy(b):this.models.sort(b);a.silent||this.trigger("reset",this,a);return this},pluck:function(a){return f.map(this.models,function(b){return b.get(a)})},reset:function(a,b){a||(a=[]);b||(b={});for(var c=0,d=this.models.length;c<d;c++)this._removeReference(this.models[c]);this._reset();this.add(a,f.extend({silent:true},
b));b.silent||this.trigger("reset",this,b);return this},fetch:function(a){a=a?f.clone(a):{};if(a.parse===void 0)a.parse=true;var b=this,c=a.success;a.success=function(d,e,f){b[a.add?"add":"reset"](b.parse(d,f),a);c&&c(b,d)};a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},create:function(a,b){var c=this,b=b?f.clone(b):{},a=this._prepareModel(a,b);if(!a)return false;b.wait||c.add(a,b);var d=b.success;b.success=function(e,f){b.wait&&c.add(e,b);d?d(e,f):e.trigger("sync",
a,f,b)};a.save(null,b);return a},parse:function(a){return a},chain:function(){return f(this.models).chain()},_reset:function(){this.length=0;this.models=[];this._byId={};this._byCid={}},_prepareModel:function(a,b){b||(b={});if(a instanceof o){if(!a.collection)a.collection=this}else{var c;b.collection=this;a=new this.model(a,b);a._validate(a.attributes,b)||(a=false)}return a},_removeReference:function(a){this==a.collection&&delete a.collection;a.off("all",this._onModelEvent,this)},_onModelEvent:function(a,
b,c,d){(a=="add"||a=="remove")&&c!=this||(a=="destroy"&&this.remove(b,d),b&&a==="change:"+b.idAttribute&&(delete this._byId[b.previous(b.idAttribute)],this._byId[b.id]=b),this.trigger.apply(this,arguments))}});f.each("forEach,each,map,reduce,reduceRight,find,detect,filter,select,reject,every,all,some,any,include,contains,invoke,max,min,sortBy,sortedIndex,toArray,size,first,initial,rest,last,without,indexOf,shuffle,lastIndexOf,isEmpty,groupBy".split(","),function(a){r.prototype[a]=function(){return f[a].apply(f,
[this.models].concat(f.toArray(arguments)))}});var u=g.Router=function(a){a||(a={});if(a.routes)this.routes=a.routes;this._bindRoutes();this.initialize.apply(this,arguments)},A=/:\w+/g,B=/\*\w+/g,C=/[-[\]{}()+?.,\\^$|#\s]/g;f.extend(u.prototype,l,{initialize:function(){},route:function(a,b,c){g.history||(g.history=new m);f.isRegExp(a)||(a=this._routeToRegExp(a));c||(c=this[b]);g.history.route(a,f.bind(function(d){d=this._extractParameters(a,d);c&&c.apply(this,d);this.trigger.apply(this,["route:"+
b].concat(d));g.history.trigger("route",this,b,d)},this));return this},navigate:function(a,b){g.history.navigate(a,b)},_bindRoutes:function(){if(this.routes){var a=[],b;for(b in this.routes)a.unshift([b,this.routes[b]]);b=0;for(var c=a.length;b<c;b++)this.route(a[b][0],a[b][1],this[a[b][1]])}},_routeToRegExp:function(a){a=a.replace(C,"\\$&").replace(A,"([^/]+)").replace(B,"(.*?)");return RegExp("^"+a+"$")},_extractParameters:function(a,b){return a.exec(b).slice(1)}});var m=g.History=function(){this.handlers=
[];f.bindAll(this,"checkUrl")},s=/^[#\/]/,D=/msie [\w.]+/;m.started=false;f.extend(m.prototype,l,{interval:50,getHash:function(a){return(a=(a?a.location:window.location).href.match(/#(.*)$/))?a[1]:""},getFragment:function(a,b){if(a==null)if(this._hasPushState||b){var a=window.location.pathname,c=window.location.search;c&&(a+=c)}else a=this.getHash();a.indexOf(this.options.root)||(a=a.substr(this.options.root.length));return a.replace(s,"")},start:function(a){if(m.started)throw Error("Backbone.history has already been started");
m.started=true;this.options=f.extend({},{root:"/"},this.options,a);this._wantsHashChange=this.options.hashChange!==false;this._wantsPushState=!!this.options.pushState;this._hasPushState=!(!this.options.pushState||!window.history||!window.history.pushState);var a=this.getFragment(),b=document.documentMode;if(b=D.exec(navigator.userAgent.toLowerCase())&&(!b||b<=7))this.iframe=i('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow,this.navigate(a);if(this._hasPushState)i(window).bind("popstate",
this.checkUrl);else if(this._wantsHashChange&&"onhashchange"in window&&!b)i(window).bind("hashchange",this.checkUrl);else if(this._wantsHashChange)this._checkUrlInterval=setInterval(this.checkUrl,this.interval);this.fragment=a;a=window.location;b=a.pathname==this.options.root;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,true),window.location.replace(this.options.root+"#"+this.fragment),true;else if(this._wantsPushState&&this._hasPushState&&
b&&a.hash)this.fragment=this.getHash().replace(s,""),window.history.replaceState({},document.title,a.protocol+"//"+a.host+this.options.root+this.fragment);if(!this.options.silent)return this.loadUrl()},stop:function(){i(window).unbind("popstate",this.checkUrl).unbind("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);m.started=false},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a==this.fragment&&this.iframe&&(a=this.getFragment(this.getHash(this.iframe)));
if(a==this.fragment)return false;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(this.getHash())},loadUrl:function(a){var b=this.fragment=this.getFragment(a);return f.any(this.handlers,function(a){if(a.route.test(b))return a.callback(b),true})},navigate:function(a,b){if(!m.started)return false;if(!b||b===true)b={trigger:b};var c=(a||"").replace(s,"");if(this.fragment!=c)this._hasPushState?(c.indexOf(this.options.root)!=0&&(c=this.options.root+c),this.fragment=c,window.history[b.replace?
"replaceState":"pushState"]({},document.title,c)):this._wantsHashChange?(this.fragment=c,this._updateHash(window.location,c,b.replace),this.iframe&&c!=this.getFragment(this.getHash(this.iframe))&&(b.replace||this.iframe.document.open().close(),this._updateHash(this.iframe.location,c,b.replace))):window.location.assign(this.options.root+a),b.trigger&&this.loadUrl(a)},_updateHash:function(a,b,c){c?a.replace(a.toString().replace(/(javascript:|#).*$/,"")+"#"+b):a.hash=b}});var v=g.View=function(a){this.cid=
f.uniqueId("view");this._configure(a||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()},E=/^(\S+)\s*(.*)$/,w="model,collection,el,id,attributes,className,tagName".split(",");f.extend(v.prototype,l,{tagName:"div",$:function(a){return this.$el.find(a)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();return this},make:function(a,b,c){a=document.createElement(a);b&&i(a).attr(b);c!=null&&i(a).html(c);return a},setElement:function(a,
b){this.$el&&this.undelegateEvents();this.$el=a instanceof i?a:i(a);this.el=this.$el[0];b!==false&&this.delegateEvents();return this},delegateEvents:function(a){if(a||(a=n(this,"events"))){this.undelegateEvents();for(var b in a){var c=a[b];f.isFunction(c)||(c=this[a[b]]);if(!c)throw Error('Method "'+a[b]+'" does not exist');var d=b.match(E),e=d[1],d=d[2],c=f.bind(c,this);e+=".delegateEvents"+this.cid;d===""?this.$el.bind(e,c):this.$el.delegate(d,e,c)}}},undelegateEvents:function(){this.$el.unbind(".delegateEvents"+
this.cid)},_configure:function(a){this.options&&(a=f.extend({},this.options,a));for(var b=0,c=w.length;b<c;b++){var d=w[b];a[d]&&(this[d]=a[d])}this.options=a},_ensureElement:function(){if(this.el)this.setElement(this.el,false);else{var a=n(this,"attributes")||{};if(this.id)a.id=this.id;if(this.className)a["class"]=this.className;this.setElement(this.make(this.tagName,a),false)}}});o.extend=r.extend=u.extend=v.extend=function(a,b){var c=F(this,a,b);c.extend=this.extend;return c};var G={create:"POST",
update:"PUT","delete":"DELETE",read:"GET"};g.sync=function(a,b,c){var d=G[a];c||(c={});var e={type:d,dataType:"json"};if(!c.url)e.url=n(b,"url")||t();if(!c.data&&b&&(a=="create"||a=="update"))e.contentType="application/json",e.data=JSON.stringify(b.toJSON());if(g.emulateJSON)e.contentType="application/x-www-form-urlencoded",e.data=e.data?{model:e.data}:{};if(g.emulateHTTP&&(d==="PUT"||d==="DELETE")){if(g.emulateJSON)e.data._method=d;e.type="POST";e.beforeSend=function(a){a.setRequestHeader("X-HTTP-Method-Override",
d)}}if(e.type!=="GET"&&!g.emulateJSON)e.processData=false;return i.ajax(f.extend(e,c))};g.wrapError=function(a,b,c){return function(d,e){e=d===b?e:d;a?a(b,e,c):b.trigger("error",b,e,c)}};var x=function(){},F=function(a,b,c){var d;d=b&&b.hasOwnProperty("constructor")?b.constructor:function(){a.apply(this,arguments)};f.extend(d,a);x.prototype=a.prototype;d.prototype=new x;b&&f.extend(d.prototype,b);c&&f.extend(d,c);d.prototype.constructor=d;d.__super__=a.prototype;return d},n=function(a,b){return!a||
!a[b]?null:f.isFunction(a[b])?a[b]():a[b]},t=function(){throw Error('A "url" property or function must be specified');};return g});

View file

@ -0,0 +1,212 @@
/*
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
*/
(function(root, doc, factory) {
if (typeof define === "function" && define.amd) {
// AMD. Register as an anonymous module.
define(["underscore"], function(_) {
return factory(_, root, doc);
});
} else {
// Browser globals
factory(root._, root, doc);
}
}(this, document, function(_, global, document, undefined) {
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;
};
var utils = {
makeFsmNamespace: (function(){
var machinaCount = 0;
return function() {
return "fsm." + machinaCount++;
};
})(),
getDefaultOptions: function() {
return {
initialState: "uninitialized",
eventListeners: {
"*" : []
},
states: {},
eventQueue: [],
namespace: utils.makeFsmNamespace()
};
}
};
var 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 : []
}
};
global.machina = machina;
return machina;
}));

View file

@ -0,0 +1,27 @@
// This is the amd module version of postal.diagnostics.js
// If you need the standard lib version, go to http://github.com/ifandelse/postal.js
define(["postal", "underscore"], function(postal, _, undefined) {
// this returns a callback that, if invoked, removes the wireTap
postal.diagnostics = postal.addWireTap(function(data, envelope) {
var all = _.extend(envelope, { data: data });
if(!JSON) {
throw "This browser or environment does not provide JSON support";
}
try {
//console.log(JSON.stringify(all));
}
catch(exception) {
try {
all.data = "ERROR: " + exception.message;
//console.log(JSON.stringify(all));
}
catch(ex) {
//console.log("Unable to parse data to JSON: " + exception);
}
}
});
});

View file

@ -0,0 +1,441 @@
/*
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.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 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 envelope = {
channel: this.channel,
topic: this._topic,
data: obj || {}
};
// 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",
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.";
}
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;
},
ignoreDuplicates: 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;
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;
}
};
var localBus = {
subscriptions: {},
wireTaps: new Array(0),
publish: function(envelope) {
_.each(this.wireTaps,function(tap) {
tap(envelope.data, envelope);
});
_.each(this.subscriptions[envelope.channel], function(topic) {
_.each(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();
}
}
}
});
});
},
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);
}
idx = subs.length - 1;
for(; idx >= 0; idx--) {
if(subs[idx].priority <= subDef.priority) {
subs.splice(idx + 1, 0, subDef);
found = true;
break;
}
}
if(!found) {
subs.unshift(subDef);
}
return subDef;
},
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;
}
}
}
},
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);
}
};
}
};
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 || "LocalChannel" ]( 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 || "LocalChannel" ]( channel, topic );
},
"3" : function( channel, topic, options ) {
return new postal.channelTypes[ options.type || "LocalChannel" ]( channel, topic );
}
};
// 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: {
LocalChannel: 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;
},
reset: function() {
// we check first in case a custom bus or resolver
// doesn't expose subscriptions or a resolver cache
if(postal.configuration.bus.subscriptions) {
postal.configuration.bus.subscriptions = {};
}
if(postal.configuration.resolver.cache) {
postal.configuration.resolver.cache = {};
}
}
};
return postal;
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,11 @@
/*
RequireJS text 1.0.2 Copyright (c) 2010-2011, The Dojo Foundation All Rights Reserved.
Available via the MIT or new BSD license.
see: http://github.com/jrburke/requirejs for details
*/
(function(){var k=["Msxml2.XMLHTTP","Microsoft.XMLHTTP","Msxml2.XMLHTTP.4.0"],n=/^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im,o=/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im,i=typeof location!=="undefined"&&location.href,p=i&&location.protocol&&location.protocol.replace(/\:/,""),q=i&&location.hostname,r=i&&(location.port||void 0),j=[];define(function(){var g,h,l;typeof window!=="undefined"&&window.navigator&&window.document?h=function(a,c){var b=g.createXhr();b.open("GET",a,!0);b.onreadystatechange=
function(){b.readyState===4&&c(b.responseText)};b.send(null)}:typeof process!=="undefined"&&process.versions&&process.versions.node?(l=require.nodeRequire("fs"),h=function(a,c){c(l.readFileSync(a,"utf8"))}):typeof Packages!=="undefined"&&(h=function(a,c){var b=new java.io.File(a),e=java.lang.System.getProperty("line.separator"),b=new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(b),"utf-8")),d,f,g="";try{d=new java.lang.StringBuffer;(f=b.readLine())&&f.length()&&
f.charAt(0)===65279&&(f=f.substring(1));for(d.append(f);(f=b.readLine())!==null;)d.append(e),d.append(f);g=String(d.toString())}finally{b.close()}c(g)});return g={version:"1.0.2",strip:function(a){if(a){var a=a.replace(n,""),c=a.match(o);c&&(a=c[1])}else a="";return a},jsEscape:function(a){return a.replace(/(['\\])/g,"\\$1").replace(/[\f]/g,"\\f").replace(/[\b]/g,"\\b").replace(/[\n]/g,"\\n").replace(/[\t]/g,"\\t").replace(/[\r]/g,"\\r")},createXhr:function(){var a,c,b;if(typeof XMLHttpRequest!==
"undefined")return new XMLHttpRequest;else for(c=0;c<3;c++){b=k[c];try{a=new ActiveXObject(b)}catch(e){}if(a){k=[b];break}}if(!a)throw Error("createXhr(): XMLHttpRequest not available");return a},get:h,parseName:function(a){var c=!1,b=a.indexOf("."),e=a.substring(0,b),a=a.substring(b+1,a.length),b=a.indexOf("!");b!==-1&&(c=a.substring(b+1,a.length),c=c==="strip",a=a.substring(0,b));return{moduleName:e,ext:a,strip:c}},xdRegExp:/^((\w+)\:)?\/\/([^\/\\]+)/,useXhr:function(a,c,b,e){var d=g.xdRegExp.exec(a),
f;if(!d)return!0;a=d[2];d=d[3];d=d.split(":");f=d[1];d=d[0];return(!a||a===c)&&(!d||d===b)&&(!f&&!d||f===e)},finishLoad:function(a,c,b,e,d){b=c?g.strip(b):b;d.isBuild&&(j[a]=b);e(b)},load:function(a,c,b,e){if(e.isBuild&&!e.inlineText)b();else{var d=g.parseName(a),f=d.moduleName+"."+d.ext,m=c.toUrl(f),h=e&&e.text&&e.text.useXhr||g.useXhr;!i||h(m,p,q,r)?g.get(m,function(c){g.finishLoad(a,d.strip,c,b,e)}):c([f],function(a){g.finishLoad(d.moduleName+"."+d.ext,d.strip,a,b,e)})}},write:function(a,c,b){if(c in
j){var e=g.jsEscape(j[c]);b.asModule(a+"!"+c,"define(function () { return '"+e+"';});\n")}},writeFile:function(a,c,b,e,d){var c=g.parseName(c),f=c.moduleName+"."+c.ext,h=b.toUrl(c.moduleName+"."+c.ext)+".js";g.load(f,b,function(){var b=function(a){return e(h,a)};b.asModule=function(a,b){return e.asModule(a,h,b)};g.write(a,f,b,d)},d)}}})})();

View file

@ -0,0 +1,31 @@
// Underscore.js 1.3.1
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the MIT license.
// Portions of Underscore are inspired or borrowed from Prototype,
// Oliver Steele's Functional, and John Resig's Micro-Templating.
// For all details and documentation:
// http://documentcloud.github.com/underscore
(function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,
h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else r._=b;b.VERSION="1.3.1";var j=b.each=
b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(d,a[e],e,a)===n)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===n)break};b.map=b.collect=function(a,c,b){var e=[];if(a==null)return e;if(x&&a.map===x)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});if(a.length===+a.length)e.length=a.length;return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=arguments.length>2;a==
null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=
function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e=
e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck=
function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&(e={value:a,computed:b})});
return e.value};b.shuffle=function(a){var b=[],d;j(a,function(a,f){f==0?b[0]=a:(d=Math.floor(Math.random()*(f+1)),b[f]=b[d],b[d]=a)});return b};b.sortBy=function(a,c,d){return b.pluck(b.map(a,function(a,b,g){return{value:a,criteria:c.call(d,a,b,g)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,
c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:a.toArray?a.toArray():b.isArray(a)?i.call(a):b.isArguments(a)?i.call(a):b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=b.head=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest=
b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,e=[];b.reduce(d,function(d,g,h){if(0==h||(c===true?b.last(d)!=g:!b.include(d,g)))d[d.length]=g,e[e.length]=a[h];return d},[]);
return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,
d){if(a==null)return-1;var e;if(d)return d=b.sortedIndex(a,c),a[d]===c?d:-1;if(p&&a.indexOf===p)return a.indexOf(c);for(d=0,e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(D&&a.lastIndexOf===D)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){arguments.length<=1&&(b=a||0,a=0);for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;)g[f++]=a,a+=d;return g};
var F=function(){};b.bind=function(a,c){var d,e;if(a.bind===s&&s)return s.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));F.prototype=a.prototype;var b=new F,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,
c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(a,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i=b.debounce(function(){h=g=false},c);return function(){d=this;e=arguments;var b;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);i()},c));g?h=true:
a.apply(d,e);i();g=true}};b.debounce=function(a,b){var d;return function(){var e=this,f=arguments;clearTimeout(d);d=setTimeout(function(){d=null;a.apply(e,f)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments,0));return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};
b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments,
1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};
b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};
b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;").replace(/\//g,"&#x2F;")};b.mixin=function(a){j(b.functions(a),
function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+
u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]=
function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=
true;return this};m.prototype.value=function(){return this._wrapped};typeof define==="function"&&define.amd&&define("underscore",function(){return b})}).call(this);

View file

@ -0,0 +1,42 @@
var socket;
require.config( {
paths : {
'text' : 'lib/requirejs-text-1.0.2',
'backbone' : 'lib/backbone',
'underscore': 'lib/underscore',
'machina' : 'lib/machina',
'postal' : 'lib/postal',
'amplify' : 'lib/amplify',
'bus' : 'infrastructure/bus'
},
baseUrl: 'js'
} );
require( [ 'backbone', 'jquery', 'underscore', 'machina', 'postal', 'lib/postal.diagnostics', 'infrastructure/postal.socket-client' ],
function( Backbone, $, _, machina, postal ){
// for debugging purposes ONLY for now:
window.postal = postal;
postal.addWireTap( function( d, e ){
if( e.topic === "search.info" ) {
console.log( JSON.stringify( e ) );
}
});
postal.connections.socket.socketMgr.on( "*", function( evnt, data ){
var args = [].slice.call( arguments,1 );
if( args[0] === "postal.remote" ) {
//console.log( "FSM Event: " + evnt + " - " + JSON.stringify( args[0] ) );
}
else {
//console.log( "FSM Event: " + evnt + " - " + JSON.stringify( args ) );
}
});
require([ 'infrastructure/app' ], function( app ) {
window.app = app;
});
});

View file

@ -0,0 +1,31 @@
define( [
'backbone',
'bus'
],
function( Backbone, bus ) {
"use strict";
return Backbone.Model.extend({
defaults: {
hashTags: []
},
initialize: function() {
var self = this;
this.subscriptions = [
bus.stats.subscribe( "hash-tag-count", function( data, env ){
if( data.hashTags && data.hashTags.length ) {
self.set( "hashTags", _.sortBy( data.hashTags, function( item ) { return item.count * -1; } ) );
}
})
];
},
dispose: function(){
_.each( this.subscriptions, function( subscription ){
subscription.unsubscribe();
});
this.clear( { silent: true } );
}
});
});

View file

@ -0,0 +1,31 @@
define( [
'backbone',
'bus'
],
function( Backbone, bus ) {
"use strict";
return Backbone.Model.extend({
defaults: {
mentions: []
},
initialize: function() {
var self = this;
this.subscriptions = [
bus.stats.subscribe( "mention-count", function( data, env ){
if( data.mentions && data.mentions.length ) {
self.set( "mentions", _.sortBy( data.mentions, function( item ) { return item.count * -1; } ) );
}
})
];
},
dispose: function(){
_.each( this.subscriptions, function( subscription ){
subscription.unsubscribe();
});
this.clear( { silent: true } );
}
});
});

View file

@ -0,0 +1,31 @@
define( [
'backbone',
'bus'
],
function( Backbone, bus ) {
"use strict";
return Backbone.Model.extend({
defaults: {
mentioners: []
},
initialize: function() {
var self = this;
this.subscriptions = [
bus.stats.subscribe( "mentioner-count", function( data, env ){
if( data.mentioners && data.mentioners.length ) {
self.set( "mentioners", _.sortBy( data.mentioners, function( item ) { return item.count * -1; } ) );
}
})
];
},
dispose: function(){
_.each( this.subscriptions, function( subscription ){
subscription.unsubscribe();
});
this.clear( { silent: true } );
}
});
});

View file

@ -0,0 +1,61 @@
define( [
'backbone',
'bus'
],
function( Backbone, bus ) {
"use strict";
return Backbone.Model.extend({
defaults: {
sessionId: "",
searchOwnership: "",
searchTerm: "",
requests: []
},
initialize: function() {
_.bindAll( this );
this.subscriptions = [
bus.app.subscribe( "search.info", this.setCurrentSearch ),
bus.app.subscribe( "search.new.ask", this.updateRequests )
];
bus.app.publish({
topic: "get.search.info",
data: {}
});
},
dispose: function(){
_.each( this.subscriptions, function( subscription ){
subscription.unsubscribe();
});
this.clear( { silent: true } );
},
setCurrentSearch: function( data, env ) {
var self = this;
self.set( "searchTerm", data.searchTerm );
postal.configuration.getSessionIdentifier(
function( id ) {
self.set( "sessionId", id, { silent: true } );
self.set( "searchOwnership",
(id === data.id)
? "You own the search."
: "You do not own the search."
);
}
);
},
updateRequests: function( data, env ) {
var reqs = this.get( "requests" );
if( !_.any( reqs, function( req ){
return req.correlationId === data.correlationId &&
req.searchTerm === data.searchTerm
})) {
reqs.push( data );
this.set( "requests", _.sortBy( reqs, function( item ) { return item.searchTerm; } ) );
}
}
});
});

View file

@ -0,0 +1,36 @@
define( [
'backbone',
'bus'
],
function( Backbone, bus ) {
"use strict";
return Backbone.Model.extend({
defaults: {
percentage: "",
clean: "",
explicit: "",
total: ""
},
initialize: function() {
var self = this;
this.subscriptions = [
bus.stats.subscribe( "profanity-percentage", function( data, env ){
self.set("percentage", data.percentage, { silent: true });
self.set("clean", data.clean, { silent: true });
self.set("explicit", data.explicit, { silent: true });
self.set("total", data.clean + data.explicit, { silent: true });
self.change();
})
];
},
dispose: function(){
_.each( this.subscriptions, function( subscription ){
subscription.unsubscribe();
});
this.clear( { silent: true } );
}
});
});

View file

@ -0,0 +1,31 @@
define( [
'backbone',
'bus'
],
function( Backbone, bus ) {
"use strict";
return Backbone.Model.extend({
defaults: {
tweeters: []
},
initialize: function() {
var self = this;
this.subscriptions = [
bus.stats.subscribe( "tweet-count", function( data, env ){
if( data.tweeters && data.tweeters.length ) {
self.set( "tweeters", _.sortBy( data.tweeters, function( item ) { return item.count * -1; } ) );
}
})
];
},
dispose: function(){
_.each( this.subscriptions, function( subscription ){
subscription.unsubscribe();
});
this.clear( { silent: true } );
}
});
});

View file

@ -0,0 +1,30 @@
define( [
'jquery',
'backbone',
'text!views/templates/container.html'
],
function( $, Backbone, template ) {
// Using ECMAScript 5 strict mode during development. By default r.js will ignore that.
"use strict";
return Backbone.View.extend( {
el: "body",
initialize: function() {
_.bindAll(this, "render");
this.template = template;
},
render: function() {
this.$el.html(this.template);
},
show: function( data ) {
this.$el.show();
},
update: function( data ) {
}
} );
} );

View file

@ -0,0 +1,41 @@
define( [
'jquery',
'backbone',
'text!views/templates/hash-tag-count.html',
'models/hash-tag-count-model',
'bus'
],
function( $, Backbone, template, HashTagCountModel, bus ) {
"use strict";
return Backbone.View.extend( {
tagName: "div",
initialize: function() {
_.bindAll( this );
this.template = _.template( template );
this.model = new HashTagCountModel();
bus.app.subscribe( "search.info", this.setCurrentSearch );
this.model.bind( "change", this.render );
this.inDom = false;
bus.stats.publish({ topic: "hash-tag-count.getLatest", data: {} });
},
render: function() {
// TODO: Capture scroll position and restore after render...
this.$el.html( this.template( this.model.toJSON() ) );
if( !this.inDom ) {
this.$el.appendTo( "#stats" );
this.inDom = true;
}
},
show: function( data ) {
this.$el.show();
},
hide: function( data ) {
this.$el.hide();
}
} );
} );

View file

@ -0,0 +1,41 @@
define( [
'jquery',
'backbone',
'text!views/templates/mention-count.html',
'models/mention-count-model',
'bus'
],
function( $, Backbone, template, MentionCountModel, bus ) {
"use strict";
return Backbone.View.extend( {
tagName: "div",
initialize: function() {
_.bindAll( this );
this.template = _.template( template );
this.model = new MentionCountModel();
bus.app.subscribe( "search.info", this.setCurrentSearch );
this.model.bind( "change", this.render );
this.inDom = false;
bus.stats.publish({ topic: "mention-count.getLatest", data: {} });
},
render: function() {
// TODO: Capture scroll position and restore after render...
this.$el.html( this.template( this.model.toJSON() ) );
if( !this.inDom ) {
this.$el.appendTo( "#stats" );
this.inDom = true;
}
},
show: function( data ) {
this.$el.show();
},
hide: function( data ) {
this.$el.hide();
}
} );
} );

View file

@ -0,0 +1,41 @@
define( [
'jquery',
'backbone',
'text!views/templates/mentioner-count.html',
'models/mentioner-count-model',
'bus'
],
function( $, Backbone, template, MentionerCountModel, bus ) {
"use strict";
return Backbone.View.extend( {
tagName: "div",
initialize: function() {
_.bindAll( this );
this.template = _.template( template );
this.model = new MentionerCountModel();
bus.app.subscribe( "search.info", this.setCurrentSearch );
this.model.bind( "change", this.render );
this.inDom = false;
bus.stats.publish({ topic: "mentioner-count.getLatest", data: {} });
},
render: function() {
// TODO: Capture scroll position and restore after render...
this.$el.html( this.template( this.model.toJSON() ) );
if( !this.inDom ) {
this.$el.appendTo( "#stats" );
this.inDom = true;
}
},
show: function( data ) {
this.$el.show();
},
hide: function( data ) {
this.$el.hide();
}
} );
} );

View file

@ -0,0 +1,52 @@
define( [
'jquery',
'backbone',
'text!views/templates/menu.html',
'bus',
'models/menu-model'
],
function( $, Backbone, template, bus, MenuModel ) {
"use strict";
return Backbone.View.extend( {
el: "#menu",
events: {
"click #btnSearch" : "updateSearch"
},
initialize: function() {
_.bindAll( this );
this.template = _.template( template );
this.model = new MenuModel();
this.model.bind( "change", this.updateView );
},
render: function() {
this.$el.html( this.template( this.model.toJSON() ) );
},
show: function( data ) {
this.$el.show();
},
updateSearch: function() {
var searchTerm = this.$el.find('#searchTerm').val();
if( searchTerm ) {
bus.app.publish({
topic: "search.new.request",
data: {
searchTerm: searchTerm
}
});
}
},
updateView: function() {
this.$el.find( "#currentSearch" ).text( this.model.get("searchTerm") );
this.$el.find( "#search-ownership").text( this.model.get("searchOwnership" ));
var reqs = this.model.get("requests").length
this.$el.find( "#request-indicator").text( reqs ? " *" : "" );
}
} );
} );

View file

@ -0,0 +1,41 @@
define( [
'jquery',
'backbone',
'text!views/templates/profanity-percentage.html',
'models/profanity-percentage-model',
'bus'
],
function( $, Backbone, template, ProfanityPercentageModel, bus ) {
"use strict";
return Backbone.View.extend( {
tagName: "div",
initialize: function() {
_.bindAll( this );
this.template = _.template( template );
this.model = new ProfanityPercentageModel();
bus.app.subscribe( "search.info", this.setCurrentSearch );
this.model.bind( "change", this.render );
this.inDom = false;
bus.stats.publish({ topic: "profanity-percentage.getLatest", data: {} });
},
render: function() {
// TODO: Capture scroll position and restore after render...
this.$el.html( this.template( this.model.toJSON() ) );
if( !this.inDom ) {
this.$el.appendTo( "#stats" );
this.inDom = true;
}
},
show: function( data ) {
this.$el.show();
},
hide: function( data ) {
this.$el.hide();
}
} );
} );

View file

@ -0,0 +1,7 @@
<div id="menu"></div>
<div id="info"></div>
<div id="stats"></div>
<div id="wiretap"></div>

View file

@ -0,0 +1,13 @@
<fieldset>
<legend><div class="title">Associated Hash Tags:</div></legend>
<div class="scrollableDiv">
<table class="hashTagTable" cellspacing="0">
<% _.each( hashTags, function( item ) { %>
<tr>
<td><span><%= item.hashTag %></span></td>
<td><span><%= item.count %></span></td>
</tr>
<% }) %>
</table>
</div>
</fieldset>

View file

@ -0,0 +1,14 @@
<fieldset>
<legend><span>Top Mentioned:</span></legend>
<div class="scrollableDiv">
<table cellspacing="0">
<% _.each( mentions, function( mention ) { %>
<tr>
<td><img class="images" src="<%= mention.image %>" /></td>
<td><span><%= mention.user %></span></td>
<td><span><%= mention.count %></span></td>
</tr>
<% }) %>
</table>
</div>
</fieldset>

View file

@ -0,0 +1,14 @@
<fieldset>
<legend><span>Top Mention-ers:</span></legend>
<div class="scrollableDiv">
<table cellspacing="0">
<% _.each( mentioners, function( mentioner ) { %>
<tr>
<td><img class="images" src="<%= mentioner.image %>" /></td>
<td><span><%= mentioner.user %></span></td>
<td><span><%= mentioner.count %></span></td>
</tr>
<% }) %>
</table>
</div>
</fieldset>

View file

@ -0,0 +1,15 @@
<div class="top-bar">
<ul class="nav-menu">
<li><a class="ps-nav" href="">Home</a></li>
<li><a class="ps-nav" href="select">Requested Searches<span id="request-indicator"></span></a></li>
<li><a class="ps-nav" href="wiretap">Show Wiretap</a></li>
</ul>
<div class="search">Searching Twitter for: <span id="currentSearch"><%= searchTerm %></span></div>
<div class="search">
<input type="text" id="searchTerm">
<input id="btnSearch" type="button" value="Search">
</div>
<div class="search" id="search-ownership"><%= searchOwnership %></div>
<div style="clear:both"></div>
</div>

View file

@ -0,0 +1,17 @@
<fieldset>
<legend><span>Profanity Rate:</span></legend>
<div>
<div>
<span>Total:</span>
<span><%= total %></span>
</div>
<div>
<span>Explicit:</span>
<span><%= explicit %></span>
</div>
<div>
<span>Profanity Rate:</span>
<span><%= percentage %>%</span>
</div>
</div>
</fieldset>

View file

@ -0,0 +1,14 @@
<fieldset>
<legend><span>Top Tweeters:</span></legend>
<div class="scrollableDiv">
<table cellspacing="0">
<% _.each( tweeters, function( tweeter ) { %>
<tr>
<td><img class="images" src="<%= tweeter.image %>" /></td>
<td><span><%= tweeter.user %></span></td>
<td><span><%= tweeter.count %></span></td>
</tr>
<% }) %>
</table>
</div>
</fieldset>

View file

@ -0,0 +1,41 @@
define( [
'jquery',
'backbone',
'text!views/templates/tweet-count.html',
'models/tweet-count-model',
'bus'
],
function( $, Backbone, template, TweetCountModel, bus ) {
"use strict";
return Backbone.View.extend( {
tagName: "div",
initialize: function() {
_.bindAll( this );
this.template = _.template( template );
this.model = new TweetCountModel();
bus.app.subscribe( "search.info", this.setCurrentSearch );
this.model.bind( "change", this.render );
this.inDom = false;
bus.stats.publish({ topic: "tweet-count.getLatest", data: {} });
},
render: function() {
// TODO: Capture scroll position and restore after render...
this.$el.html( this.template( this.model.toJSON() ) );
if( !this.inDom ) {
this.$el.appendTo( "#stats" );
this.inDom = true;
}
},
show: function( data ) {
this.$el.show();
},
hide: function( data ) {
this.$el.hide();
}
} );
} );

View file

@ -0,0 +1,63 @@
var _ = require('underscore'),
_scanner = function(regExp, text, callback) {
var match;
while(text.search(regExp) !== -1) {
match = regExp.exec(text);
if(match && match[1]) {
callback(match[1].toLowerCase());
}
text = text.replace(regExp, "");
}
},
HashTagCount = function(namespace) {
this.namespace = namespace;
this.events = {};
this.hashTags = { list: [], registry: {} };
this.lastStats = undefined;
};
HashTagCount.prototype = {
init: function() {
this.hashTags = { list: [], registry: {} };
this.lastStats = undefined;
},
on: function( eventName, callback ) {
if( !this.events[ eventName ] ) {
this.events[ eventName ] = [];
}
this.events[ eventName ].push( callback );
return function() {
this.events[ eventName ] = _.without( this.events[ eventName ], callback );
}.bind( this );
},
raiseEvent: function( eventName, data ) {
if( this.events[ eventName ] ) {
this.events[ eventName ].forEach( function( callback ){
callback( data );
});
}
},
processNewTweets: function( tweets ) {
tweets.forEach( function( tweet ){
this.processOtherHashTags( tweet.text );
}, this );
this.lastStats = { type: "HashTagCount", hashTags: this.hashTags.list };
this.raiseEvent( this.namespace, this.lastStats );
},
processOtherHashTags: function( text ) {
_scanner( /#(\w*)/i, text, function( hash ){
if( !this.hashTags.registry[ hash ] ) {
var obj = { hashTag: hash, count: 0 };
this.hashTags.registry[ hash ] = obj;
this.hashTags.list.push( obj );
}
this.hashTags.registry[ hash ].count++;
}.bind( this ));
}
};
module.exports = HashTagCount;

View file

@ -0,0 +1,73 @@
var _ = require('underscore'),
_scanner = function(regExp, text, callback) {
var match;
while(text.search(regExp) !== -1) {
match = regExp.exec(text);
if(match && match[1]) {
callback(match[1].toLowerCase());
}
text = text.replace(regExp, "");
}
},
MentionCount = function(namespace) {
this.namespace = namespace;
this.events = {};
this.mentions = { list: [], registry: {} };
this.userImageMap = {};
this.lastStats = undefined;
};
MentionCount.prototype = {
init: function() {
this.mentions = { list: [], registry: {} };
this.lastStats = undefined;
},
on: function( eventName, callback ) {
if( !this.events[ eventName ] ) {
this.events[ eventName ] = [];
}
this.events[ eventName ].push( callback );
return function() {
this.events[ eventName ] = _.without( this.events[ eventName ], callback );
}.bind( this );
},
raiseEvent: function( eventName, data ) {
if( this.events[ eventName ] ) {
this.events[ eventName ].forEach( function( callback ){
callback( data );
});
}
},
processNewTweets: function(tweets) {
tweets.forEach(function(tweet){
this.userImageMap[ tweet.from_user ] = tweet.profile_image_url;
this.processMentions(tweet);
}, this);
this.tryToMatchProfileImages();
this.lastStats = { type: "MentionCount", mentions: this.mentions.list };
this.raiseEvent( this.namespace, this.lastStats );
},
tryToMatchProfileImages: function() {
_.each( this.mentions.registry, function( v, k ){
if( this.userImageMap[ k ] ) {
v.image = this.userImageMap[ k ];
}
}, this );
},
processMentions: function(tweet) {
_scanner(/@(\w*)/i, tweet.text, function(mentioned) {
if(!this.mentions.registry[mentioned]) {
var obj = { user: mentioned, count: 0, image: "images/default_profile_1_normal.png" };
this.mentions.registry[mentioned] = obj;
this.mentions.list.push(obj);
}
this.mentions.registry[mentioned].count++;
}.bind(this));
}
};
module.exports = MentionCount;

View file

@ -0,0 +1,62 @@
var _ = require('underscore'),
_scanner = function(regExp, text, callback) {
var match;
while(text.search(regExp) !== -1) {
match = regExp.exec(text);
if(match && match[1]) {
callback(match[1].toLowerCase());
}
text = text.replace(regExp, "");
}
},
MentionerCount = function(namespace) {
this.namespace = namespace;
this.events = {};
this.mentioners = { list: [], registry: {} };
this.lastStats = undefined;
};
MentionerCount.prototype = {
init: function() {
this.mentioners = { list: [], registry: {} };
this.lastStats = undefined;
},
on: function( eventName, callback ) {
if( !this.events[ eventName ] ) {
this.events[ eventName ] = [];
}
this.events[ eventName ].push( callback );
return function() {
this.events[ eventName ] = _.without( this.events[ eventName ], callback );
}.bind( this );
},
raiseEvent: function( eventName, data ) {
if( this.events[ eventName ] ) {
this.events[ eventName ].forEach( function( callback ){
callback( data );
});
}
},
processNewTweets: function(tweets) {
tweets.forEach(function(tweet){
this.processMentioners(tweet);
}, this);
this.lastStats = { type: "MentionerCount", mentioners: this.mentioners.list };
this.raiseEvent( this.namespace, this.lastStats );
},
processMentioners: function(tweet) {
_scanner(/@(\w*)/i, tweet.text, function(mentioned) {
if(!this.mentioners.registry[tweet.from_user]) {
var obj = { user: tweet.from_user, count: 0, image: tweet.profile_image_url };
this.mentioners.registry[tweet.from_user] = obj;
this.mentioners.list.push(obj);
}
this.mentioners.registry[tweet.from_user].count++;
}.bind(this));
}
};
module.exports = MentionerCount;

View file

@ -0,0 +1,109 @@
// quick hack array - not going to go regex crazy for now
var _badWords = [
/\bfuck\b/i,
/\bfucks\b/i,
/\bfucking\b/i,
/\bfucked\b/i,
/\bfucker\b/i,
/\bfuckers\b/i,
/\bass\b/i,
/\basses\b/i,
/\basshole\b/i,
/\bassholes\b/i,
/\bshit\b/i,
/\bshitted\b/i,
/\bshitter\b/i,
/\bshitters\b/i,
/\bshitting\b/i,
/\bshithead\b/i,
/\bcunt\b/i,
/\bcunts\b/i,
/\bpussy\b/i,
/\bdick\b/i,
/\bdicks\b/i,
/\bdickhead\b/i,
/\bdickheads\b/i,
/\bdamn\b/i,
/\bdamned\b/i,
/\bdamning\b/i,
/\bcock\b/i,
/\bpenis\b/i,
/\bfag\b/i,
/\bbitch\b/i,
/\bfaggot\b/,
/\bpiss\b/,
/\bpissing\b/
],
_ = require('underscore'),
ProfanityPercentage = function(namespace) {
this.namespace = namespace;
this.events = {};
this.profanityStats = { clean: 0, explicit: 0 };
this.lastStats = undefined;
};
ProfanityPercentage.prototype = {
init: function() {
this.profanityStats = { clean: 0, explicit: 0 };
this.lastStats = undefined;
},
on: function( eventName, callback ) {
if( !this.events[ eventName ] ) {
this.events[ eventName ] = [];
}
this.events[ eventName ].push( callback );
return function() {
this.events[ eventName ] = _.without( this.events[ eventName ], callback );
}.bind( this );
},
raiseEvent: function( eventName, data ) {
if( this.events[ eventName ] ) {
this.events[ eventName ].forEach( function( callback ){
callback( data );
});
}
},
hasProfanity: function(text) {
for(var i = 0; i < _badWords.length; i++) {
if(text.search(_badWords[i]) !== -1)
{
return true;
}
}
return false;
},
getPercentage: function() {
var total = (this.profanityStats.clean + this.profanityStats.explicit);
if(total === 0) {
return 0;
}
else {
return ((this.profanityStats.explicit / total) * 100).toFixed(2);
}
},
processNewTweets: function(tweets) {
tweets.forEach(function(tweet){
this.profanitize(tweet);
}, this);
this.lastStats = {
type: "ProfanityPercentage",
percentage: this.getPercentage(),
clean: this.profanityStats.clean,
explicit: this.profanityStats.explicit
};
this.raiseEvent( this.namespace, this.lastStats );
},
profanitize: function(tweet) {
if(this.hasProfanity(tweet.text)) {
this.profanityStats.explicit++;
}
else {
this.profanityStats.clean++;
}
}
};
module.exports = ProfanityPercentage;

View file

@ -0,0 +1,52 @@
var _ = require('underscore'),
TweetCount = function(namespace) {
this.namespace = namespace;
this.events = {};
this.tweeters = { list: [], registry: {} };
this.lastStats = undefined;
};
TweetCount.prototype = {
init: function() {
this.tweeters = { list: [], registry: {} };
this.lastStats = undefined;
},
on: function( eventName, callback ) {
if( !this.events[ eventName ] ) {
this.events[ eventName ] = [];
}
this.events[ eventName ].push( callback );
return function() {
this.events[ eventName ] = _.without( this.events[ eventName ], callback );
}.bind( this );
},
raiseEvent: function( eventName, data ) {
if( this.events[ eventName ] ) {
this.events[ eventName ].forEach( function( callback ){
callback.call( this, data );
});
}
},
processNewTweets: function(tweets) {
tweets.forEach(function(tweet){
this.processTweetCount(tweet);
}, this);
this.lastStats = { type: "TweetCount", tweeters: this.tweeters.list };
this.raiseEvent( this.namespace, this.lastStats );
},
processTweetCount : function(tweet) {
if(tweet.from_user) {
if(!this.tweeters.registry[tweet.from_user]) {
var obj = {user: tweet.from_user, count: 0, image: tweet.profile_image_url};
this.tweeters.registry[tweet.from_user] = obj;
this.tweeters.list.push(obj);
}
this.tweeters.registry[tweet.from_user].count++;
}
}
};
module.exports = TweetCount;

156
example/node/index.js Normal file
View file

@ -0,0 +1,156 @@
var _ = require("underscore"),
express = require( 'express' ),
app = express.createServer(),
postal = require( "./messaging/postal.js" ),
PostalSocketHost = require( './messaging/postal.socket-host.js' ),
sockets = require( './messaging/socket-io-provider.js' ),
TwitterSearch = require( './twitter-search.js' ),
collectors = require( './stat-collectors.js' ),
BusAdapter = require( './messaging/bus-adapter.js' ),
machina = require( './machina.js' );
postal.addWireTap( function( data, envelope ){
if( envelope.channel === "stats" /*|| envelope.channel === "twittersearch"*/ ) {
var env = _.extend( {}, envelope );
delete env.data;
console.log( JSON.stringify( env ) );
}
else if (_.include(["postal.socket", "postal", "app", "app.events"], envelope.channel) ) {
console.log( JSON.stringify( envelope ) );
}
});
// wire machina FSMs into postal automagically
require( './messaging/machina.postal.js' )( postal, machina );
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", "*" );
postal.linkChannels( { channel: "postal.socket", topic: "client.migrated"}, { channel: "statsApp", topic: "client.migrated" } );
return new machina.Fsm({
namespace: "statsApp",
currentSearch: {
id: null,
searchTerm: "{ Search Not Active }"
},
searchRequests: {},
express: app,
appChannel: appChannel,
searchChannel: searchChannel,
statsChannel: statsChannel,
searchAgent: new BusAdapter(new TwitterSearch( refreshinterval), searchChannel, searchChannel ),
stats: collectors.load( searchChannel, statsChannel ),
postal: postal,
bridge: new PostalSocketHost( postal, sockets.getSocketProvider( postal, app ) ),
getAvailableStats: function( clientId ) {
this.appChannel.publish({
topic: "available.topics",
correlationId: clientId,
data: {
topics:_.toArray(this.stats).map(function(stat) { return { channel: "stats", topic: stat.namespace }; })
}
});
},
setSearch: function( correlationId, searchTerm ) {
this.currentSearch = {
id: correlationId,
searchTerm: searchTerm
};
this.searchAgent.search(searchTerm);
this.appChannel.publish({
topic: "search.info",
data: this.currentSearch
});
},
getSearchInfo: function( env ) {
this.appChannel.publish({
topic: "search.info",
correlationId: env.correlationId,
data: this.currentSearch
});
},
states: {
uninitialized: {
start: function() {
this.transition("notSearching");
},
"*" : function() {
this.deferUntilTransition();
}
},
notSearching: {
"search.new.request" : function( data, envelope ) {
this.setSearch( envelope.correlationId, data.searchTerm );
this.transition("searching");
},
"get.search.info": function( data, env ) {
this.getSearchInfo( env );
},
"get.available" : function( data, envelope ) {
this.getAvailableStats( envelope.correlationId );
}
},
searching: {
"search.new.request" : function( data, envelope ) {
if(envelope.correlationId === this.currentSearch.id) {
this.setSearch( envelope.correlationId, data.searchTerm );
}
else {
this.appChannel.publish({
topic: "search.new.ask",
data: {
correlationId: this.currentSearch.id,
searchTerm: data.searchTerm
}
});
}
},
"search.new.confirm" : function( data, envelope ) {
if( envelope.correlationId === this.currentSearch.id ) {
this.setSearch( data.correlationId, data.searchTerm );
}
},
"get.search.info": function( data, env ) {
this.getSearchInfo( env );
},
"get.available" : function( data, envelope ) {
this.getAvailableStats( envelope.correlationId );
},
"client.migrated" : function( data, envelope ) {
if( data.lastSessionId === this.currentSearch.id ) {
this.currentSearch.id = data.sessionId;
}
}
}
}
});
};
var x = module.exports = new TwitterSocketStats( 8002, 7000 );
x.on("*", function(evnt, data){
console.log("FSM Event: " + evnt + " - " + JSON.stringify([].slice.call(arguments,1)));
});
x.handle("start");

199
example/node/machina.js Normal file
View file

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

View file

@ -0,0 +1,17 @@
module.exports = function( target, searchChannel, statsChannel ) {
target.bus = {
subscriptions: [],
publishers: [
target.on( "newTweets", function( data ) {
statsChannel.publish( { topic: "newTweets", data: data } );
} ),
target.on( "search.current", function( data ) {
searchChannel.publish( { topic: "search.current", data: data } );
} ),
target.on( "search.nodata", function( data ) {
searchChannel.publish( { topic: "search.nodata", data: data } );
} )
]
};
return target;
};

View file

@ -0,0 +1,22 @@
module.exports = function( target, searchChannel, statChannel ) {
target.bus = {
subscriptions: [
searchChannel.subscribe( "init", target.init ).withContext( target ),
searchChannel.subscribe( "newTweets", target.processNewTweets ).withContext( target ),
statChannel.subscribe( target.namespace + ".getLatest", function( data, env ){
console.log("GET LATEST FOR: " + target.namespace);
statChannel.publish( {
topic: target.namespace,
data: target.lastStats,
correlationId: env.correlationId
})
}).withContext( target )
],
publishers: [
target.on( target.namespace, function( data ) {
statChannel.publish( { topic: target.namespace, data: data } );
} )
]
};
return target;
};

View file

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

View file

@ -0,0 +1,25 @@
var _ = require('underscore');
module.exports = function(postal) {
// this returns a callback that, if invoked, removes the wireTap
postal.diagnostics = postal.addWireTap(function(data, envelope) {
var all = _.extend(envelope, { data: data });
if(!JSON) {
throw "This browser or environment does not provide JSON support";
}
try {
console.log(JSON.stringify(all));
}
catch(exception) {
try {
all.data = "ERROR: " + exception.message;
console.log(JSON.stringify(all));
}
catch(ex) {
console.log("Unable to parse data to JSON: " + exception);
}
}
});
};

View file

@ -0,0 +1,440 @@
/*
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.0
*/
// This is the node.js version of postal.js
// If you need the standard or amd client lib version, go to http://github.com/ifandelse/postal.js
var _ = require('underscore');
var DEFAULT_CHANNEL = "/",
DEFAULT_PRIORITY = 50,
DEFAULT_DISPOSEAFTER = 0,
SYSTEM_CHANNEL = "postal",
NO_OP = function() { };
var DistinctPredicate = 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 envelope = {
channel: this.channel,
topic: this._topic,
data: obj || {}
};
// 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",
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.";
}
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;
},
ignoreDuplicates: 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;
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;
}
};
var localBus = {
subscriptions: {},
wireTaps: new Array(0),
publish: function(envelope) {
_.each(this.wireTaps,function(tap) {
tap(envelope.data, envelope);
});
_.each(this.subscriptions[envelope.channel], function(topic) {
_.each(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();
}
}
}
});
});
},
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);
}
idx = subs.length - 1;
for(; idx >= 0; idx--) {
if(subs[idx].priority <= subDef.priority) {
subs.splice(idx + 1, 0, subDef);
found = true;
break;
}
}
if(!found) {
subs.unshift(subDef);
}
return subDef;
},
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;
}
}
}
},
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);
}
};
}
};
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 || "LocalChannel" ]( 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 || "LocalChannel" ]( channel, topic );
},
"3" : function( channel, topic, options ) {
return new postal.channelTypes[ options.type || "LocalChannel" ]( channel, topic );
}
};
// 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: {
LocalChannel: 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;
},
reset: function() {
// we check first in case a custom bus or resolver
// doesn't expose subscriptions or a resolver cache
if(postal.configuration.bus.subscriptions) {
postal.configuration.bus.subscriptions = {};
}
if(postal.configuration.resolver.cache) {
postal.configuration.resolver.cache = {};
}
}
};
module.exports = postal;

View file

@ -0,0 +1,246 @@
var _ = require('underscore'),
machina = require('../machina.js');
/*
A RemoteClientProxy instance is a finite state machine that manages
how a socket is used, and how it is (or isn't) allowed to interact
with the server-side instance of postal, depending on the state
related to the connection and the client on the other end of the socket.
*/
var RemoteClientProxy = function( postal, socketClient, bridge ) {
// helper function to handle when a socket client wants
// to set up a subscription to server-side messages
var subscribe = function( data ) {
var self = this;
if( !self.subscriptions[ data.channel ] ) {
self.subscriptions[ data.channel ] = {};
}
if( !self.subscriptions[ data.channel ][ data.topic ] ) {
self.subscriptions[ data.channel ][ data.topic ] =
postal.subscribe({
channel : data.channel,
topic : data.topic,
callback: function( d, e ) {
self.handle( "socketTransmit", d, e );
}
}).withConstraint(function( data, env ) {
if( !env.correlationId ) {
return true;
}
return env.correlationId === self.sessionId;
});
}
},
// The actual FSM managing the socketClient
fsm = new machina.Fsm({
// handle to the socket client wrapper
socket: socketClient,
// object used to store (and lookup) subscriptions owned by this connection/client
subscriptions: {},
// method to allow a RemoteClientProxy to hand off state to another RemoteClientProxy
// as part of a migration
getClientState: function() {
return {
queuedMsgs: _.filter( this.eventQueue, function( item ) {
return item.args[0] === "socketTransmit";
})
};
},
sessionId: null,
states: {
// we start here after an explicit transition from uninitialized
// the only message we are interested in is "clientId" - in which
// the client tells us who they are, and we determine if we need
// to move through a migration first, or go directly to 'connected'
connecting: {
"*" : function() {
this.deferUntilTransition();
},
clientId : function( data ) {
this.sessionId = data.sessionId;
if( bridge.enableMigration && data.lastSessionId && data.lastSessionId !== data.sessionId ) {
this.lastSessionId = data.lastSessionId;
this.transition( "migrating" );
}
else {
this.transition( "connected" );
}
}
},
// we're going to get any relevant state from the old RemoteClientProxy
// and apply it to this instance. This includes the publishing of
// messages that got queued up at the old RemoteClientProxy.
migrating: {
// We tell the client to handle it's migration work (if any) first.
_onEnter: function() {
this.socket.migrateClientSubscriptions();
},
// Once the client has completed its migration tasks, it will
// publish a message that triggers this handler. This is where we
// transfer queued messages from the old RemoteClientProxy, etc.
migrationComplete: function ( data ) {
if( bridge.clientMap[ this.lastSessionId ] ){
var priorState = bridge.clientMap[ this.lastSessionId ].getClientState();
_.each(priorState.queuedMsgs, function( item ) {
this.eventQueue.unshift( item );
}, this );
}
postal.publish( {
channel: "postal.socket",
topic: "client.migrated",
data: {
sessionId: this.socket.id,
lastSessionId: this.lastSessionId
}
} );
this.transition( "connected" );
},
subscribe : subscribe,
"*": function() {
this.deferUntilTransition();
}
},
// We're online and this is how to handle it....
connected: {
_onEnter: function() {
bridge.clientMap[ this.sessionId ] = this;
this.socket.confirmClientIdentified( this.sessionId );
},
disconnect: function() {
this.transition( "disconnected" );
},
subscribe : subscribe,
unsubscribe: function( data ) {
if( this.subscriptions[ data.channel ] && this.subscriptions[ data.channel ][ data.topic ] ) {
this.subscriptions[ data.channel ] && this.subscriptions[ data.channel ][ data.topic ].unsubscribe();
}
},
publish: function( envelope ) {
postal.publish( envelope );
},
socketTransmit: function( data, envelope ) {
this.socket.publish.call( this.socket, data, envelope );
}
},
disconnected: {
_onEnter: function() {
postal.publish( {
channel: "postal.socket",
topic: "client.disconnect",
data: {
sessionId: this.sessionId
}
} );
},
"*" : function() {
this.deferUntilTransition();
},
socketTransmit: function() {
this.deferUntilTransition("connected");
}
}
}
});
// These are all the events from our Socket Client wrapper to which we need to listen.
_.each([ "disconnect", "subscribe", "unsubscribe", "publish", "clientId", "migrationComplete" ], function ( evnt ) {
socketClient.on( evnt, function( data ) {
fsm.handle( evnt, data );
});
});
// This is currently here for debugging purposes only
fsm.on("*", function(evnt, data){
var args = [].slice.call(arguments,1);
if(evnt === "Deferred" || args[0] === "socketTransmit") {
console.log("Socket FSM: " + evnt + " - " + JSON.stringify(args[0]));
}
else {
console.log("Socket FSM: " + evnt + " - " + JSON.stringify(args));
}
});
// every RemoteClientProxy gets a private subscription that can talk to the client
// apart from any application-level channels. This is a "utility" conduit.
fsm.subscriptions._direct = {
_private: postal.subscribe({
channel : socketClient.id,
topic : "*",
callback: function(d, e) {
fsm.handle("socketTransmit", d, e);
}
})
};
// we explicitly start the FSM now that we've wired everything up, etc.
fsm.transition( "connecting" );
return fsm;
};
var PostalSocketHost = function( postal, socketProvider ) {
var self = this;
// if enableMigration === true, then clients are allowed to migrate
// from an older session id to a new one. This can happen when the
// client's connection is lost, but the old session id is kept around
// in memory or local storage, etc. When the client reconnects, it
// will transmit the current and previous (if applicable) session ids.
// If this is set to true, then the migration will take place - which
// includes transferring existing subscriptions, as well as the delivery
// of messages sent to the old session id proxy after the client lost
// connection.
self.enableMigration = true;
self.channel = postal.channel("postal.socket", "client.connect");
// array of clients - confirmed or not
self.clients = [];
// once a client has been marked as "identified", they are added to
// this object, with the session id as the key.
self.clientMap = {};
// how to clean up a client
self.removeClient = function( id ) {
if( self.clientMap[ id ] ) {
_.each( self.clientMap[ id ].subscriptions, function( channel ){
_.each( channel, function( sub ){
console.log("unsubscribing: " + sub.channel + " - " + sub.topic);
sub.unsubscribe();
});
});
delete self.clientMap[ id ];
}
self.clients = _.filter(self.clients, function( item ) {
return item.sessionId !== id;
});
};
self.provider = socketProvider;
// when our provider indicates a new socket has connected, we pass
// the socket wrapper to an fsm (RemoteClientProxy) to be managed by it.
self.provider.on( "connect", function( socket ) {
self.clients.push( new RemoteClientProxy( postal, socket, self ) );
});
// when a client disconnects, if enableMigration is true, we don't delete
// the old session until it has been used in a migration
self.channel.subscribe("client.disconnect", function ( data, envelope ){
if( !self.enableMigration ) {
self.removeClient( data.sessionId );
}
});
// if a session has been used in a migration and is no longer needed, remove it
self.channel.subscribe("client.migrated", function ( data, envelope ){
self.removeClient( data.lastSessionId );
});
};
module.exports = PostalSocketHost;

View file

@ -0,0 +1,113 @@
var _ = require('underscore'),
EventEmitter = require('events').EventEmitter,
util = require('util');
module.exports = {
getSocketProvider:function( postal, app, options ) {
/*
Socket client wrappers must provide the following:
1.) an "id" property which returns the session identifier for the connection
2.) EventEmitter's "on" method call (to subscribe callbacks to events)
3.) publish the following events (consumed by the postal socket host:
a.) disconnect - triggered when the socket disconnects.
b.) publish - triggered when the client at the other end of the socket
publishes a message. The callback(s) for this event should receive
a valid postal.js message envelope, and should ideally include a
correlationId that matches the client wrapper id.
{
channel: "someChannel",
topic: "some.topic",
data: {
greeting: "Oh, hai!"
},
timeStamp: "2012-04-17T07:11:14.731Z",
correlationId: "195371348624696856"
}
c.) unsubscribe - triggered when the client at the other end of the socket
specified that they want to unsubscribe a server-side subscription tied
to the socket. The callback(s) for this event should receive an object
that contains the channel name and topic to unsubscribe:
{ channel: "someChannel", topic: "some.topic" }
d.) subscribe - triggered when the client at the other end of the socket
specifies that they want to subscribe on the server-side, and have messages
forwarded over the socket down the client. The callback(s) for this event
should receive an object that contains the channel name and topic to which
the client wants to subscribe:
{ channel: "someChannel", topic: "some.topic" }
e.) clientId - triggered when the client sends a message containing the current
and previous (if one exists) session id. The callback(s) for this event
should receive an object similar to the following:
{ sessionId: "5072013211555684761", lastSessionId: "15075244651115973710" }
f.) migrationComplete - triggered when the client is done migrating any state from
an old session id to the current one. The callbacks do not need an arg provided.
*/
var SocketIoClient = function( socket ) {
var self = this;
EventEmitter.call(self);
self.socket = socket;
Object.defineProperty( self, "id", {
get: function() {
return self.socket.id;
},
enumerable : true
});
// wire up what to do when this socket disconnects
self.socket.on( 'disconnect', function () {
self.emit( "disconnect", self );
});
// set up event hooks for postal specific events
_.each( [ "publish", "unsubscribe", "subscribe", "clientId", "migrationComplete" ], function( evnt ) {
self.socket.on( "postal." + evnt, function( data ){
self.emit( evnt, data );
} );
});
};
util.inherits(SocketIoClient, EventEmitter);
SocketIoClient.prototype.publish = function( data, envelope ) {
this.socket.emit( "postal.socket.remote", envelope );
};
SocketIoClient.prototype.confirmClientIdentified = function( uid ) {
this.socket.emit( "postal.socket.identified", { uid: uid } );
};
SocketIoClient.prototype.migrateClientSubscriptions = function() {
this.socket.emit( "postal.socket.migration", {} );
};
/*
The Socket Provider is a wrapper around the framework being
used to provide websocket functionality to postal.socket.
The implementation below wraps socket.io.
*/
var SocketIoProvider = function( webApp, opt ) {
var opt = opt || {},
io = require( 'socket.io' ).listen( webApp ),
self = this;
io.set( 'log level', opt.ioLogLevel || 1 );
EventEmitter.call(self);
self.events = {
"connect" : []
};
// We listen for when socket.io detects a new socket connection.
// Then we take that socket instance and wrap it in our SocketIoClient
// wrapper. The PostalSocketHost receives an instance of this
// SocketIoProvider object and subscribes to the "connect" event.
io.sockets.on( "connection", function( socket ) {
var _socket = new SocketIoClient( socket );
self.emit("connect", _socket);
});
};
util.inherits(SocketIoProvider, EventEmitter);
return new SocketIoProvider( app, options );
}
};

416
example/node/node_modules/.bin/express generated vendored Executable file
View file

@ -0,0 +1,416 @@
#!/usr/bin/env node
/**
* Module dependencies.
*/
var fs = require('fs')
, os = require('os')
, exec = require('child_process').exec
, mkdirp = require('mkdirp');
/**
* Framework version.
*/
var version = '2.5.8';
/**
* Add session support.
*/
var sessions = false;
/**
* CSS engine to utilize.
*/
var cssEngine;
/**
* End-of-line code.
*/
var eol = os.platform
? ('win32' == os.platform() ? '\r\n' : '\n')
: '\n';
/**
* Template engine to utilize.
*/
var templateEngine = 'jade';
/**
* Usage documentation.
*/
var usage = ''
+ '\n'
+ ' Usage: express [options] [path]\n'
+ '\n'
+ ' Options:\n'
+ ' -s, --sessions add session support\n'
+ ' -t, --template <engine> add template <engine> support (jade|ejs). default=jade\n'
+ ' -c, --css <engine> add stylesheet <engine> support (stylus). default=plain css\n'
+ ' -v, --version output framework version\n'
+ ' -h, --help output help information\n'
;
/**
* Routes index template.
*/
var index = [
''
, '/*'
, ' * GET home page.'
, ' */'
, ''
, 'exports.index = function(req, res){'
, ' res.render(\'index\', { title: \'Express\' })'
, '};'
].join(eol);
/**
* Jade layout template.
*/
var jadeLayout = [
'!!!'
, 'html'
, ' head'
, ' title= title'
, ' link(rel=\'stylesheet\', href=\'/stylesheets/style.css\')'
, ' body!= body'
].join(eol);
/**
* Jade index template.
*/
var jadeIndex = [
'h1= title'
, 'p Welcome to #{title}'
].join(eol);
/**
* EJS layout template.
*/
var ejsLayout = [
'<!DOCTYPE html>'
, '<html>'
, ' <head>'
, ' <title><%= title %></title>'
, ' <link rel=\'stylesheet\' href=\'/stylesheets/style.css\' />'
, ' </head>'
, ' <body>'
, ' <%- body %>'
, ' </body>'
, '</html>'
].join(eol);
/**
* EJS index template.
*/
var ejsIndex = [
'<h1><%= title %></h1>'
, '<p>Welcome to <%= title %></p>'
].join(eol);
/**
* Default css template.
*/
var css = [
'body {'
, ' padding: 50px;'
, ' font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;'
, '}'
, ''
, 'a {'
, ' color: #00B7FF;'
, '}'
].join(eol);
/**
* Default stylus template.
*/
var stylus = [
'body'
, ' padding: 50px'
, ' font: 14px "Lucida Grande", Helvetica, Arial, sans-serif'
, 'a'
, ' color: #00B7FF'
].join(eol);
/**
* App template.
*/
var app = [
''
, '/**'
, ' * Module dependencies.'
, ' */'
, ''
, 'var express = require(\'express\')'
, ' , routes = require(\'./routes\');'
, ''
, 'var app = module.exports = express.createServer();'
, ''
, '// Configuration'
, ''
, 'app.configure(function(){'
, ' app.set(\'views\', __dirname + \'/views\');'
, ' app.set(\'view engine\', \':TEMPLATE\');'
, ' app.use(express.bodyParser());'
, ' app.use(express.methodOverride());{sess}{css}'
, ' app.use(app.router);'
, ' app.use(express.static(__dirname + \'/public\'));'
, '});'
, ''
, 'app.configure(\'development\', function(){'
, ' app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));'
, '});'
, ''
, 'app.configure(\'production\', function(){'
, ' app.use(express.errorHandler());'
, '});'
, ''
, '// Routes'
, ''
, 'app.get(\'/\', routes.index);'
, ''
, 'app.listen(3000);'
, 'console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);'
, ''
].join(eol);
// Parse arguments
var args = process.argv.slice(2)
, path = '.';
while (args.length) {
var arg = args.shift();
switch (arg) {
case '-h':
case '--help':
abort(usage);
break;
case '-v':
case '--version':
abort(version);
break;
case '-s':
case '--session':
case '--sessions':
sessions = true;
break;
case '-c':
case '--css':
args.length
? (cssEngine = args.shift())
: abort('--css requires an argument');
break;
case '-t':
case '--template':
args.length
? (templateEngine = args.shift())
: abort('--template requires an argument');
break;
default:
path = arg;
}
}
// Generate application
(function createApplication(path) {
emptyDirectory(path, function(empty){
if (empty) {
createApplicationAt(path);
} else {
confirm('destination is not empty, continue? ', function(ok){
if (ok) {
process.stdin.destroy();
createApplicationAt(path);
} else {
abort('aborting');
}
});
}
});
})(path);
/**
* Create application at the given directory `path`.
*
* @param {String} path
*/
function createApplicationAt(path) {
console.log();
process.on('exit', function(){
console.log();
console.log(' dont forget to install dependencies:');
console.log(' $ cd %s && npm install', path);
console.log();
});
mkdir(path, function(){
mkdir(path + '/public');
mkdir(path + '/public/javascripts');
mkdir(path + '/public/images');
mkdir(path + '/public/stylesheets', function(){
switch (cssEngine) {
case 'stylus':
write(path + '/public/stylesheets/style.styl', stylus);
break;
default:
write(path + '/public/stylesheets/style.css', css);
}
});
mkdir(path + '/routes', function(){
write(path + '/routes/index.js', index);
});
mkdir(path + '/views', function(){
switch (templateEngine) {
case 'ejs':
write(path + '/views/layout.ejs', ejsLayout);
write(path + '/views/index.ejs', ejsIndex);
break;
case 'jade':
write(path + '/views/layout.jade', jadeLayout);
write(path + '/views/index.jade', jadeIndex);
break;
}
});
// CSS Engine support
switch (cssEngine) {
case 'stylus':
app = app.replace('{css}', eol + ' app.use(require(\'stylus\').middleware({ src: __dirname + \'/public\' }));');
break;
default:
app = app.replace('{css}', '');
}
// Session support
app = app.replace('{sess}', sessions
? eol + ' app.use(express.cookieParser());' + eol + ' app.use(express.session({ secret: \'your secret here\' }));'
: '');
// Template support
app = app.replace(':TEMPLATE', templateEngine);
// package.json
var json = '{' + eol;
json += ' "name": "application-name"' + eol;
json += ' , "version": "0.0.1"' + eol;
json += ' , "private": true' + eol;
json += ' , "dependencies": {' + eol;
json += ' "express": "' + version + '"' + eol;
if (cssEngine) json += ' , "' + cssEngine + '": ">= 0.0.1"' + eol;
if (templateEngine) json += ' , "' + templateEngine + '": ">= 0.0.1"' + eol;
json += ' }' + eol;
json += '}';
write(path + '/package.json', json);
write(path + '/app.js', app);
});
}
/**
* Check if the given directory `path` is empty.
*
* @param {String} path
* @param {Function} fn
*/
function emptyDirectory(path, fn) {
fs.readdir(path, function(err, files){
if (err && 'ENOENT' != err.code) throw err;
fn(!files || !files.length);
});
}
/**
* echo str > path.
*
* @param {String} path
* @param {String} str
*/
function write(path, str) {
fs.writeFile(path, str);
console.log(' \x1b[36mcreate\x1b[0m : ' + path);
}
/**
* Prompt confirmation with the given `msg`.
*
* @param {String} msg
* @param {Function} fn
*/
function confirm(msg, fn) {
prompt(msg, function(val){
fn(/^ *y(es)?/i.test(val));
});
}
/**
* Prompt input with the given `msg` and callback `fn`.
*
* @param {String} msg
* @param {Function} fn
*/
function prompt(msg, fn) {
// prompt
if (' ' == msg[msg.length - 1]) {
process.stdout.write(msg);
} else {
console.log(msg);
}
// stdin
process.stdin.setEncoding('ascii');
process.stdin.once('data', function(data){
fn(data);
}).resume();
}
/**
* Mkdir -p.
*
* @param {String} path
* @param {Function} fn
*/
function mkdir(path, fn) {
mkdirp(path, 0755, function(err){
if (err) throw err;
console.log(' \033[36mcreate\033[0m : ' + path);
fn && fn();
});
}
/**
* Exit with the given `str`.
*
* @param {String} str
*/
function abort(str) {
console.error(str);
process.exit(1);
}

7
example/node/node_modules/express/.npmignore generated vendored Normal file
View file

@ -0,0 +1,7 @@
.git*
docs/
examples/
support/
test/
testing.js
.DS_Store

805
example/node/node_modules/express/History.md generated vendored Normal file
View file

@ -0,0 +1,805 @@
2.5.8 / 2012-02-08
==================
* Update mkdirp dep. Closes #991
2.5.7 / 2012-02-06
==================
* Fixed `app.all` duplicate DELETE requests [mscdex]
2.5.6 / 2012-01-13
==================
* Updated hamljs dev dep. Closes #953
2.5.5 / 2012-01-08
==================
* Fixed: set `filename` on cached templates [matthewleon]
2.5.4 / 2012-01-02
==================
* Fixed `express(1)` eol on 0.4.x. Closes #947
2.5.3 / 2011-12-30
==================
* Fixed `req.is()` when a charset is present
2.5.2 / 2011-12-10
==================
* Fixed: express(1) LF -> CRLF for windows
2.5.1 / 2011-11-17
==================
* Changed: updated connect to 1.8.x
* Removed sass.js support from express(1)
2.5.0 / 2011-10-24
==================
* Added ./routes dir for generated app by default
* Added npm install reminder to express(1) app gen
* Added 0.5.x support
* Removed `make test-cov` since it wont work with node 0.5.x
* Fixed express(1) public dir for windows. Closes #866
2.4.7 / 2011-10-05
==================
* Added mkdirp to express(1). Closes #795
* Added simple _json-config_ example
* Added shorthand for the parsed request's pathname via `req.path`
* Changed connect dep to 1.7.x to fix npm issue...
* Fixed `res.redirect()` __HEAD__ support. [reported by xerox]
* Fixed `req.flash()`, only escape args
* Fixed absolute path checking on windows. Closes #829 [reported by andrewpmckenzie]
2.4.6 / 2011-08-22
==================
* Fixed multiple param callback regression. Closes #824 [reported by TroyGoode]
2.4.5 / 2011-08-19
==================
* Added support for routes to handle errors. Closes #809
* Added `app.routes.all()`. Closes #803
* Added "basepath" setting to work in conjunction with reverse proxies etc.
* Refactored `Route` to use a single array of callbacks
* Added support for multiple callbacks for `app.param()`. Closes #801
Closes #805
* Changed: removed .call(self) for route callbacks
* Dependency: `qs >= 0.3.1`
* Fixed `res.redirect()` on windows due to `join()` usage. Closes #808
2.4.4 / 2011-08-05
==================
* Fixed `res.header()` intention of a set, even when `undefined`
* Fixed `*`, value no longer required
* Fixed `res.send(204)` support. Closes #771
2.4.3 / 2011-07-14
==================
* Added docs for `status` option special-case. Closes #739
* Fixed `options.filename`, exposing the view path to template engines
2.4.2. / 2011-07-06
==================
* Revert "removed jsonp stripping" for XSS
2.4.1 / 2011-07-06
==================
* Added `res.json()` JSONP support. Closes #737
* Added _extending-templates_ example. Closes #730
* Added "strict routing" setting for trailing slashes
* Added support for multiple envs in `app.configure()` calls. Closes #735
* Changed: `res.send()` using `res.json()`
* Changed: when cookie `path === null` don't default it
* Changed; default cookie path to "home" setting. Closes #731
* Removed _pids/logs_ creation from express(1)
2.4.0 / 2011-06-28
==================
* Added chainable `res.status(code)`
* Added `res.json()`, an explicit version of `res.send(obj)`
* Added simple web-service example
2.3.12 / 2011-06-22
==================
* \#express is now on freenode! come join!
* Added `req.get(field, param)`
* Added links to Japanese documentation, thanks @hideyukisaito!
* Added; the `express(1)` generated app outputs the env
* Added `content-negotiation` example
* Dependency: connect >= 1.5.1 < 2.0.0
* Fixed view layout bug. Closes #720
* Fixed; ignore body on 304. Closes #701
2.3.11 / 2011-06-04
==================
* Added `npm test`
* Removed generation of dummy test file from `express(1)`
* Fixed; `express(1)` adds express as a dep
* Fixed; prune on `prepublish`
2.3.10 / 2011-05-27
==================
* Added `req.route`, exposing the current route
* Added _package.json_ generation support to `express(1)`
* Fixed call to `app.param()` function for optional params. Closes #682
2.3.9 / 2011-05-25
==================
* Fixed bug-ish with `../' in `res.partial()` calls
2.3.8 / 2011-05-24
==================
* Fixed `app.options()`
2.3.7 / 2011-05-23
==================
* Added route `Collection`, ex: `app.get('/user/:id').remove();`
* Added support for `app.param(fn)` to define param logic
* Removed `app.param()` support for callback with return value
* Removed module.parent check from express(1) generated app. Closes #670
* Refactored router. Closes #639
2.3.6 / 2011-05-20
==================
* Changed; using devDependencies instead of git submodules
* Fixed redis session example
* Fixed markdown example
* Fixed view caching, should not be enabled in development
2.3.5 / 2011-05-20
==================
* Added export `.view` as alias for `.View`
2.3.4 / 2011-05-08
==================
* Added `./examples/say`
* Fixed `res.sendfile()` bug preventing the transfer of files with spaces
2.3.3 / 2011-05-03
==================
* Added "case sensitive routes" option.
* Changed; split methods supported per rfc [slaskis]
* Fixed route-specific middleware when using the same callback function several times
2.3.2 / 2011-04-27
==================
* Fixed view hints
2.3.1 / 2011-04-26
==================
* Added `app.match()` as `app.match.all()`
* Added `app.lookup()` as `app.lookup.all()`
* Added `app.remove()` for `app.remove.all()`
* Added `app.remove.VERB()`
* Fixed template caching collision issue. Closes #644
* Moved router over from connect and started refactor
2.3.0 / 2011-04-25
==================
* Added options support to `res.clearCookie()`
* Added `res.helpers()` as alias of `res.locals()`
* Added; json defaults to UTF-8 with `res.send()`. Closes #632. [Daniel * Dependency `connect >= 1.4.0`
* Changed; auto set Content-Type in res.attachement [Aaron Heckmann]
* Renamed "cache views" to "view cache". Closes #628
* Fixed caching of views when using several apps. Closes #637
* Fixed gotcha invoking `app.param()` callbacks once per route middleware.
Closes #638
* Fixed partial lookup precedence. Closes #631
Shaw]
2.2.2 / 2011-04-12
==================
* Added second callback support for `res.download()` connection errors
* Fixed `filename` option passing to template engine
2.2.1 / 2011-04-04
==================
* Added `layout(path)` helper to change the layout within a view. Closes #610
* Fixed `partial()` collection object support.
Previously only anything with `.length` would work.
When `.length` is present one must still be aware of holes,
however now `{ collection: {foo: 'bar'}}` is valid, exposes
`keyInCollection` and `keysInCollection`.
* Performance improved with better view caching
* Removed `request` and `response` locals
* Changed; errorHandler page title is now `Express` instead of `Connect`
2.2.0 / 2011-03-30
==================
* Added `app.lookup.VERB()`, ex `app.lookup.put('/user/:id')`. Closes #606
* Added `app.match.VERB()`, ex `app.match.put('/user/12')`. Closes #606
* Added `app.VERB(path)` as alias of `app.lookup.VERB()`.
* Dependency `connect >= 1.2.0`
2.1.1 / 2011-03-29
==================
* Added; expose `err.view` object when failing to locate a view
* Fixed `res.partial()` call `next(err)` when no callback is given [reported by aheckmann]
* Fixed; `res.send(undefined)` responds with 204 [aheckmann]
2.1.0 / 2011-03-24
==================
* Added `<root>/_?<name>` partial lookup support. Closes #447
* Added `request`, `response`, and `app` local variables
* Added `settings` local variable, containing the app's settings
* Added `req.flash()` exception if `req.session` is not available
* Added `res.send(bool)` support (json response)
* Fixed stylus example for latest version
* Fixed; wrap try/catch around `res.render()`
2.0.0 / 2011-03-17
==================
* Fixed up index view path alternative.
* Changed; `res.locals()` without object returns the locals
2.0.0rc3 / 2011-03-17
==================
* Added `res.locals(obj)` to compliment `res.local(key, val)`
* Added `res.partial()` callback support
* Fixed recursive error reporting issue in `res.render()`
2.0.0rc2 / 2011-03-17
==================
* Changed; `partial()` "locals" are now optional
* Fixed `SlowBuffer` support. Closes #584 [reported by tyrda01]
* Fixed .filename view engine option [reported by drudge]
* Fixed blog example
* Fixed `{req,res}.app` reference when mounting [Ben Weaver]
2.0.0rc / 2011-03-14
==================
* Fixed; expose `HTTPSServer` constructor
* Fixed express(1) default test charset. Closes #579 [reported by secoif]
* Fixed; default charset to utf-8 instead of utf8 for lame IE [reported by NickP]
2.0.0beta3 / 2011-03-09
==================
* Added support for `res.contentType()` literal
The original `res.contentType('.json')`,
`res.contentType('application/json')`, and `res.contentType('json')`
will work now.
* Added `res.render()` status option support back
* Added charset option for `res.render()`
* Added `.charset` support (via connect 1.0.4)
* Added view resolution hints when in development and a lookup fails
* Added layout lookup support relative to the page view.
For example while rendering `./views/user/index.jade` if you create
`./views/user/layout.jade` it will be used in favour of the root layout.
* Fixed `res.redirect()`. RFC states absolute url [reported by unlink]
* Fixed; default `res.send()` string charset to utf8
* Removed `Partial` constructor (not currently used)
2.0.0beta2 / 2011-03-07
==================
* Added res.render() `.locals` support back to aid in migration process
* Fixed flash example
2.0.0beta / 2011-03-03
==================
* Added HTTPS support
* Added `res.cookie()` maxAge support
* Added `req.header()` _Referrer_ / _Referer_ special-case, either works
* Added mount support for `res.redirect()`, now respects the mount-point
* Added `union()` util, taking place of `merge(clone())` combo
* Added stylus support to express(1) generated app
* Added secret to session middleware used in examples and generated app
* Added `res.local(name, val)` for progressive view locals
* Added default param support to `req.param(name, default)`
* Added `app.disabled()` and `app.enabled()`
* Added `app.register()` support for omitting leading ".", either works
* Added `res.partial()`, using the same interface as `partial()` within a view. Closes #539
* Added `app.param()` to map route params to async/sync logic
* Added; aliased `app.helpers()` as `app.locals()`. Closes #481
* Added extname with no leading "." support to `res.contentType()`
* Added `cache views` setting, defaulting to enabled in "production" env
* Added index file partial resolution, eg: partial('user') may try _views/user/index.jade_.
* Added `req.accepts()` support for extensions
* Changed; `res.download()` and `res.sendfile()` now utilize Connect's
static file server `connect.static.send()`.
* Changed; replaced `connect.utils.mime()` with npm _mime_ module
* Changed; allow `req.query` to be pre-defined (via middleware or other parent
* Changed view partial resolution, now relative to parent view
* Changed view engine signature. no longer `engine.render(str, options, callback)`, now `engine.compile(str, options) -> Function`, the returned function accepts `fn(locals)`.
* Fixed `req.param()` bug returning Array.prototype methods. Closes #552
* Fixed; using `Stream#pipe()` instead of `sys.pump()` in `res.sendfile()`
* Fixed; using _qs_ module instead of _querystring_
* Fixed; strip unsafe chars from jsonp callbacks
* Removed "stream threshold" setting
1.0.8 / 2011-03-01
==================
* Allow `req.query` to be pre-defined (via middleware or other parent app)
* "connect": ">= 0.5.0 < 1.0.0". Closes #547
* Removed the long deprecated __EXPRESS_ENV__ support
1.0.7 / 2011-02-07
==================
* Fixed `render()` setting inheritance.
Mounted apps would not inherit "view engine"
1.0.6 / 2011-02-07
==================
* Fixed `view engine` setting bug when period is in dirname
1.0.5 / 2011-02-05
==================
* Added secret to generated app `session()` call
1.0.4 / 2011-02-05
==================
* Added `qs` dependency to _package.json_
* Fixed namespaced `require()`s for latest connect support
1.0.3 / 2011-01-13
==================
* Remove unsafe characters from JSONP callback names [Ryan Grove]
1.0.2 / 2011-01-10
==================
* Removed nested require, using `connect.router`
1.0.1 / 2010-12-29
==================
* Fixed for middleware stacked via `createServer()`
previously the `foo` middleware passed to `createServer(foo)`
would not have access to Express methods such as `res.send()`
or props like `req.query` etc.
1.0.0 / 2010-11-16
==================
* Added; deduce partial object names from the last segment.
For example by default `partial('forum/post', postObject)` will
give you the _post_ object, providing a meaningful default.
* Added http status code string representation to `res.redirect()` body
* Added; `res.redirect()` supporting _text/plain_ and _text/html_ via __Accept__.
* Added `req.is()` to aid in content negotiation
* Added partial local inheritance [suggested by masylum]. Closes #102
providing access to parent template locals.
* Added _-s, --session[s]_ flag to express(1) to add session related middleware
* Added _--template_ flag to express(1) to specify the
template engine to use.
* Added _--css_ flag to express(1) to specify the
stylesheet engine to use (or just plain css by default).
* Added `app.all()` support [thanks aheckmann]
* Added partial direct object support.
You may now `partial('user', user)` providing the "user" local,
vs previously `partial('user', { object: user })`.
* Added _route-separation_ example since many people question ways
to do this with CommonJS modules. Also view the _blog_ example for
an alternative.
* Performance; caching view path derived partial object names
* Fixed partial local inheritance precedence. [reported by Nick Poulden] Closes #454
* Fixed jsonp support; _text/javascript_ as per mailinglist discussion
1.0.0rc4 / 2010-10-14
==================
* Added _NODE_ENV_ support, _EXPRESS_ENV_ is deprecated and will be removed in 1.0.0
* Added route-middleware support (very helpful, see the [docs](http://expressjs.com/guide.html#Route-Middleware))
* Added _jsonp callback_ setting to enable/disable jsonp autowrapping [Dav Glass]
* Added callback query check on response.send to autowrap JSON objects for simple webservice implementations [Dav Glass]
* Added `partial()` support for array-like collections. Closes #434
* Added support for swappable querystring parsers
* Added session usage docs. Closes #443
* Added dynamic helper caching. Closes #439 [suggested by maritz]
* Added authentication example
* Added basic Range support to `res.sendfile()` (and `res.download()` etc)
* Changed; `express(1)` generated app using 2 spaces instead of 4
* Default env to "development" again [aheckmann]
* Removed _context_ option is no more, use "scope"
* Fixed; exposing _./support_ libs to examples so they can run without installs
* Fixed mvc example
1.0.0rc3 / 2010-09-20
==================
* Added confirmation for `express(1)` app generation. Closes #391
* Added extending of flash formatters via `app.flashFormatters`
* Added flash formatter support. Closes #411
* Added streaming support to `res.sendfile()` using `sys.pump()` when >= "stream threshold"
* Added _stream threshold_ setting for `res.sendfile()`
* Added `res.send()` __HEAD__ support
* Added `res.clearCookie()`
* Added `res.cookie()`
* Added `res.render()` headers option
* Added `res.redirect()` response bodies
* Added `res.render()` status option support. Closes #425 [thanks aheckmann]
* Fixed `res.sendfile()` responding with 403 on malicious path
* Fixed `res.download()` bug; when an error occurs remove _Content-Disposition_
* Fixed; mounted apps settings now inherit from parent app [aheckmann]
* Fixed; stripping Content-Length / Content-Type when 204
* Fixed `res.send()` 204. Closes #419
* Fixed multiple _Set-Cookie_ headers via `res.header()`. Closes #402
* Fixed bug messing with error handlers when `listenFD()` is called instead of `listen()`. [thanks guillermo]
1.0.0rc2 / 2010-08-17
==================
* Added `app.register()` for template engine mapping. Closes #390
* Added `res.render()` callback support as second argument (no options)
* Added callback support to `res.download()`
* Added callback support for `res.sendfile()`
* Added support for middleware access via `express.middlewareName()` vs `connect.middlewareName()`
* Added "partials" setting to docs
* Added default expresso tests to `express(1)` generated app. Closes #384
* Fixed `res.sendfile()` error handling, defer via `next()`
* Fixed `res.render()` callback when a layout is used [thanks guillermo]
* Fixed; `make install` creating ~/.node_libraries when not present
* Fixed issue preventing error handlers from being defined anywhere. Closes #387
1.0.0rc / 2010-07-28
==================
* Added mounted hook. Closes #369
* Added connect dependency to _package.json_
* Removed "reload views" setting and support code
development env never caches, production always caches.
* Removed _param_ in route callbacks, signature is now
simply (req, res, next), previously (req, res, params, next).
Use _req.params_ for path captures, _req.query_ for GET params.
* Fixed "home" setting
* Fixed middleware/router precedence issue. Closes #366
* Fixed; _configure()_ callbacks called immediately. Closes #368
1.0.0beta2 / 2010-07-23
==================
* Added more examples
* Added; exporting `Server` constructor
* Added `Server#helpers()` for view locals
* Added `Server#dynamicHelpers()` for dynamic view locals. Closes #349
* Added support for absolute view paths
* Added; _home_ setting defaults to `Server#route` for mounted apps. Closes #363
* Added Guillermo Rauch to the contributor list
* Added support for "as" for non-collection partials. Closes #341
* Fixed _install.sh_, ensuring _~/.node_libraries_ exists. Closes #362 [thanks jf]
* Fixed `res.render()` exceptions, now passed to `next()` when no callback is given [thanks guillermo]
* Fixed instanceof `Array` checks, now `Array.isArray()`
* Fixed express(1) expansion of public dirs. Closes #348
* Fixed middleware precedence. Closes #345
* Fixed view watcher, now async [thanks aheckmann]
1.0.0beta / 2010-07-15
==================
* Re-write
- much faster
- much lighter
- Check [ExpressJS.com](http://expressjs.com) for migration guide and updated docs
0.14.0 / 2010-06-15
==================
* Utilize relative requires
* Added Static bufferSize option [aheckmann]
* Fixed caching of view and partial subdirectories [aheckmann]
* Fixed mime.type() comments now that ".ext" is not supported
* Updated haml submodule
* Updated class submodule
* Removed bin/express
0.13.0 / 2010-06-01
==================
* Added node v0.1.97 compatibility
* Added support for deleting cookies via Request#cookie('key', null)
* Updated haml submodule
* Fixed not-found page, now using using charset utf-8
* Fixed show-exceptions page, now using using charset utf-8
* Fixed view support due to fs.readFile Buffers
* Changed; mime.type() no longer accepts ".type" due to node extname() changes
0.12.0 / 2010-05-22
==================
* Added node v0.1.96 compatibility
* Added view `helpers` export which act as additional local variables
* Updated haml submodule
* Changed ETag; removed inode, modified time only
* Fixed LF to CRLF for setting multiple cookies
* Fixed cookie complation; values are now urlencoded
* Fixed cookies parsing; accepts quoted values and url escaped cookies
0.11.0 / 2010-05-06
==================
* Added support for layouts using different engines
- this.render('page.html.haml', { layout: 'super-cool-layout.html.ejs' })
- this.render('page.html.haml', { layout: 'foo' }) // assumes 'foo.html.haml'
- this.render('page.html.haml', { layout: false }) // no layout
* Updated ext submodule
* Updated haml submodule
* Fixed EJS partial support by passing along the context. Issue #307
0.10.1 / 2010-05-03
==================
* Fixed binary uploads.
0.10.0 / 2010-04-30
==================
* Added charset support via Request#charset (automatically assigned to 'UTF-8' when respond()'s
encoding is set to 'utf8' or 'utf-8'.
* Added "encoding" option to Request#render(). Closes #299
* Added "dump exceptions" setting, which is enabled by default.
* Added simple ejs template engine support
* Added error reponse support for text/plain, application/json. Closes #297
* Added callback function param to Request#error()
* Added Request#sendHead()
* Added Request#stream()
* Added support for Request#respond(304, null) for empty response bodies
* Added ETag support to Request#sendfile()
* Added options to Request#sendfile(), passed to fs.createReadStream()
* Added filename arg to Request#download()
* Performance enhanced due to pre-reversing plugins so that plugins.reverse() is not called on each request
* Performance enhanced by preventing several calls to toLowerCase() in Router#match()
* Changed; Request#sendfile() now streams
* Changed; Renamed Request#halt() to Request#respond(). Closes #289
* Changed; Using sys.inspect() instead of JSON.encode() for error output
* Changed; run() returns the http.Server instance. Closes #298
* Changed; Defaulting Server#host to null (INADDR_ANY)
* Changed; Logger "common" format scale of 0.4f
* Removed Logger "request" format
* Fixed; Catching ENOENT in view caching, preventing error when "views/partials" is not found
* Fixed several issues with http client
* Fixed Logger Content-Length output
* Fixed bug preventing Opera from retaining the generated session id. Closes #292
0.9.0 / 2010-04-14
==================
* Added DSL level error() route support
* Added DSL level notFound() route support
* Added Request#error()
* Added Request#notFound()
* Added Request#render() callback function. Closes #258
* Added "max upload size" setting
* Added "magic" variables to collection partials (\_\_index\_\_, \_\_length\_\_, \_\_isFirst\_\_, \_\_isLast\_\_). Closes #254
* Added [haml.js](http://github.com/visionmedia/haml.js) submodule; removed haml-js
* Added callback function support to Request#halt() as 3rd/4th arg
* Added preprocessing of route param wildcards using param(). Closes #251
* Added view partial support (with collections etc)
* Fixed bug preventing falsey params (such as ?page=0). Closes #286
* Fixed setting of multiple cookies. Closes #199
* Changed; view naming convention is now NAME.TYPE.ENGINE (for example page.html.haml)
* Changed; session cookie is now httpOnly
* Changed; Request is no longer global
* Changed; Event is no longer global
* Changed; "sys" module is no longer global
* Changed; moved Request#download to Static plugin where it belongs
* Changed; Request instance created before body parsing. Closes #262
* Changed; Pre-caching views in memory when "cache view contents" is enabled. Closes #253
* Changed; Pre-caching view partials in memory when "cache view partials" is enabled
* Updated support to node --version 0.1.90
* Updated dependencies
* Removed set("session cookie") in favour of use(Session, { cookie: { ... }})
* Removed utils.mixin(); use Object#mergeDeep()
0.8.0 / 2010-03-19
==================
* Added coffeescript example app. Closes #242
* Changed; cache api now async friendly. Closes #240
* Removed deprecated 'express/static' support. Use 'express/plugins/static'
0.7.6 / 2010-03-19
==================
* Added Request#isXHR. Closes #229
* Added `make install` (for the executable)
* Added `express` executable for setting up simple app templates
* Added "GET /public/*" to Static plugin, defaulting to <root>/public
* Added Static plugin
* Fixed; Request#render() only calls cache.get() once
* Fixed; Namespacing View caches with "view:"
* Fixed; Namespacing Static caches with "static:"
* Fixed; Both example apps now use the Static plugin
* Fixed set("views"). Closes #239
* Fixed missing space for combined log format
* Deprecated Request#sendfile() and 'express/static'
* Removed Server#running
0.7.5 / 2010-03-16
==================
* Added Request#flash() support without args, now returns all flashes
* Updated ext submodule
0.7.4 / 2010-03-16
==================
* Fixed session reaper
* Changed; class.js replacing js-oo Class implementation (quite a bit faster, no browser cruft)
0.7.3 / 2010-03-16
==================
* Added package.json
* Fixed requiring of haml / sass due to kiwi removal
0.7.2 / 2010-03-16
==================
* Fixed GIT submodules (HAH!)
0.7.1 / 2010-03-16
==================
* Changed; Express now using submodules again until a PM is adopted
* Changed; chat example using millisecond conversions from ext
0.7.0 / 2010-03-15
==================
* Added Request#pass() support (finds the next matching route, or the given path)
* Added Logger plugin (default "common" format replaces CommonLogger)
* Removed Profiler plugin
* Removed CommonLogger plugin
0.6.0 / 2010-03-11
==================
* Added seed.yml for kiwi package management support
* Added HTTP client query string support when method is GET. Closes #205
* Added support for arbitrary view engines.
For example "foo.engine.html" will now require('engine'),
the exports from this module are cached after the first require().
* Added async plugin support
* Removed usage of RESTful route funcs as http client
get() etc, use http.get() and friends
* Removed custom exceptions
0.5.0 / 2010-03-10
==================
* Added ext dependency (library of js extensions)
* Removed extname() / basename() utils. Use path module
* Removed toArray() util. Use arguments.values
* Removed escapeRegexp() util. Use RegExp.escape()
* Removed process.mixin() dependency. Use utils.mixin()
* Removed Collection
* Removed ElementCollection
* Shameless self promotion of ebook "Advanced JavaScript" (http://dev-mag.com) ;)
0.4.0 / 2010-02-11
==================
* Added flash() example to sample upload app
* Added high level restful http client module (express/http)
* Changed; RESTful route functions double as HTTP clients. Closes #69
* Changed; throwing error when routes are added at runtime
* Changed; defaulting render() context to the current Request. Closes #197
* Updated haml submodule
0.3.0 / 2010-02-11
==================
* Updated haml / sass submodules. Closes #200
* Added flash message support. Closes #64
* Added accepts() now allows multiple args. fixes #117
* Added support for plugins to halt. Closes #189
* Added alternate layout support. Closes #119
* Removed Route#run(). Closes #188
* Fixed broken specs due to use(Cookie) missing
0.2.1 / 2010-02-05
==================
* Added "plot" format option for Profiler (for gnuplot processing)
* Added request number to Profiler plugin
* Fixed binary encoding for multi-part file uploads, was previously defaulting to UTF8
* Fixed issue with routes not firing when not files are present. Closes #184
* Fixed process.Promise -> events.Promise
0.2.0 / 2010-02-03
==================
* Added parseParam() support for name[] etc. (allows for file inputs with "multiple" attr) Closes #180
* Added Both Cache and Session option "reapInterval" may be "reapEvery". Closes #174
* Added expiration support to cache api with reaper. Closes #133
* Added cache Store.Memory#reap()
* Added Cache; cache api now uses first class Cache instances
* Added abstract session Store. Closes #172
* Changed; cache Memory.Store#get() utilizing Collection
* Renamed MemoryStore -> Store.Memory
* Fixed use() of the same plugin several time will always use latest options. Closes #176
0.1.0 / 2010-02-03
==================
* Changed; Hooks (before / after) pass request as arg as well as evaluated in their context
* Updated node support to 0.1.27 Closes #169
* Updated dirname(__filename) -> __dirname
* Updated libxmljs support to v0.2.0
* Added session support with memory store / reaping
* Added quick uid() helper
* Added multi-part upload support
* Added Sass.js support / submodule
* Added production env caching view contents and static files
* Added static file caching. Closes #136
* Added cache plugin with memory stores
* Added support to StaticFile so that it works with non-textual files.
* Removed dirname() helper
* Removed several globals (now their modules must be required)
0.0.2 / 2010-01-10
==================
* Added view benchmarks; currently haml vs ejs
* Added Request#attachment() specs. Closes #116
* Added use of node's parseQuery() util. Closes #123
* Added `make init` for submodules
* Updated Haml
* Updated sample chat app to show messages on load
* Updated libxmljs parseString -> parseHtmlString
* Fixed `make init` to work with older versions of git
* Fixed specs can now run independant specs for those who cant build deps. Closes #127
* Fixed issues introduced by the node url module changes. Closes 126.
* Fixed two assertions failing due to Collection#keys() returning strings
* Fixed faulty Collection#toArray() spec due to keys() returning strings
* Fixed `make test` now builds libxmljs.node before testing
0.0.1 / 2010-01-03
==================
* Initial release

22
example/node/node_modules/express/LICENSE generated vendored Normal file
View file

@ -0,0 +1,22 @@
(The MIT License)
Copyright (c) 2009-2011 TJ Holowaychuk <tj@vision-media.ca>
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.

29
example/node/node_modules/express/Makefile generated vendored Normal file
View file

@ -0,0 +1,29 @@
DOCS = $(shell find docs/*.md)
HTMLDOCS = $(DOCS:.md=.html)
TESTS = $(shell find test/*.test.js)
test:
@NODE_ENV=test ./node_modules/.bin/expresso $(TESTS)
docs: $(HTMLDOCS)
@ echo "... generating TOC"
@./support/toc.js docs/guide.html
%.html: %.md
@echo "... $< -> $@"
@markdown $< \
| cat docs/layout/head.html - docs/layout/foot.html \
> $@
site:
rm -fr /tmp/docs \
&& cp -fr docs /tmp/docs \
&& git checkout gh-pages \
&& cp -fr /tmp/docs/* . \
&& echo "done"
docclean:
rm -f docs/*.{1,html}
.PHONY: site test docs docclean

145
example/node/node_modules/express/Readme.md generated vendored Normal file
View file

@ -0,0 +1,145 @@
# Express
Insanely fast (and small) server-side JavaScript web development framework
built on [node](http://nodejs.org) and [Connect](http://github.com/senchalabs/connect).
var app = express.createServer();
app.get('/', function(req, res){
res.send('Hello World');
});
app.listen(3000);
## Installation
$ npm install express
or to access the `express(1)` executable install globally:
$ npm install -g express
## Quick Start
The quickest way to get started with express is to utilize the executable `express(1)` to generate an application as shown below:
Create the app:
$ npm install -g express
$ express /tmp/foo && cd /tmp/foo
Install dependencies:
$ npm install -d
Start the server:
$ node app.js
## Features
* Robust routing
* Redirection helpers
* Dynamic view helpers
* Content negotiation
* Focus on high performance
* View rendering and partials support
* Environment based configuration
* Session based flash notifications
* Built on [Connect](http://github.com/senchalabs/connect)
* High test coverage
* Executable for generating applications quickly
* Application level view options
Via Connect:
* Session support
* Cache API
* Mime helpers
* ETag support
* Persistent flash notifications
* Cookie support
* JSON-RPC
* Logging
* and _much_ more!
## Contributors
The following are the major contributors of Express (in no specific order).
* TJ Holowaychuk ([visionmedia](http://github.com/visionmedia))
* Ciaran Jessup ([ciaranj](http://github.com/ciaranj))
* Aaron Heckmann ([aheckmann](http://github.com/aheckmann))
* Guillermo Rauch ([guille](http://github.com/guille))
## More Information
* #express on freenode
* [express-expose](http://github.com/visionmedia/express-expose) expose objects, functions, modules and more to client-side js with ease
* [express-configure](http://github.com/visionmedia/express-configuration) async configuration support
* [express-messages](http://github.com/visionmedia/express-messages) flash notification rendering helper
* [express-namespace](http://github.com/visionmedia/express-namespace) namespaced route support
* [express-params](https://github.com/visionmedia/express-params) param pre-condition functions
* [express-mongoose](https://github.com/LearnBoost/express-mongoose) plugin for easy rendering of Mongoose async Query results
* Follow [tjholowaychuk](http://twitter.com/tjholowaychuk) on twitter for updates
* [Google Group](http://groups.google.com/group/express-js) for discussion
* Visit the [Wiki](http://github.com/visionmedia/express/wiki)
* [日本語ドキュメンテーション](http://hideyukisaito.com/doc/expressjs/) by [hideyukisaito](https://github.com/hideyukisaito)
* Screencast - [Introduction](http://bit.ly/eRYu0O)
* Screencast - [View Partials](http://bit.ly/dU13Fx)
* Screencast - [Route Specific Middleware](http://bit.ly/hX4IaH)
* Screencast - [Route Path Placeholder Preconditions](http://bit.ly/eNqmVs)
## Node Compatibility
Express 1.x is compatible with node 0.2.x and connect < 1.0.
Express 2.x is compatible with node 0.4.x or 0.6.x, and connect 1.x
Express 3.x (master) will be compatible with node 0.6.x and connect 2.x
## Viewing Examples
First install the dev dependencies to install all the example / test suite deps:
$ npm install
then run whichever tests you want:
$ node examples/jade/app.js
## Running Tests
To run the test suite first invoke the following command within the repo, installing the development dependencies:
$ npm install
then run the tests:
$ make test
## License
(The MIT License)
Copyright (c) 2009-2011 TJ Holowaychuk &lt;tj@vision-media.ca&gt;
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.

2
example/node/node_modules/express/index.js generated vendored Normal file
View file

@ -0,0 +1,2 @@
module.exports = require('./lib/express');

79
example/node/node_modules/express/lib/express.js generated vendored Normal file
View file

@ -0,0 +1,79 @@
/*!
* Express
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var connect = require('connect')
, HTTPSServer = require('./https')
, HTTPServer = require('./http')
, Route = require('./router/route')
/**
* Re-export connect auto-loaders.
*
* This prevents the need to `require('connect')` in order
* to access core middleware, so for example `express.logger()` instead
* of `require('connect').logger()`.
*/
var exports = module.exports = connect.middleware;
/**
* Framework version.
*/
exports.version = '2.5.8';
/**
* Shortcut for `new Server(...)`.
*
* @param {Function} ...
* @return {Server}
* @api public
*/
exports.createServer = function(options){
if ('object' == typeof options) {
return new HTTPSServer(options, Array.prototype.slice.call(arguments, 1));
} else {
return new HTTPServer(Array.prototype.slice.call(arguments));
}
};
/**
* Expose constructors.
*/
exports.HTTPServer = HTTPServer;
exports.HTTPSServer = HTTPSServer;
exports.Route = Route;
/**
* View extensions.
*/
exports.View =
exports.view = require('./view');
/**
* Response extensions.
*/
require('./response');
/**
* Request extensions.
*/
require('./request');
// Error handler title
exports.errorHandler.title = 'Express';

582
example/node/node_modules/express/lib/http.js generated vendored Normal file
View file

@ -0,0 +1,582 @@
/*!
* Express - HTTPServer
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var qs = require('qs')
, connect = require('connect')
, router = require('./router')
, Router = require('./router')
, view = require('./view')
, toArray = require('./utils').toArray
, methods = router.methods.concat('del', 'all')
, url = require('url')
, utils = connect.utils;
/**
* Expose `HTTPServer`.
*/
exports = module.exports = HTTPServer;
/**
* Server proto.
*/
var app = HTTPServer.prototype;
/**
* Initialize a new `HTTPServer` with optional `middleware`.
*
* @param {Array} middleware
* @api public
*/
function HTTPServer(middleware){
connect.HTTPServer.call(this, []);
this.init(middleware);
};
/**
* Inherit from `connect.HTTPServer`.
*/
app.__proto__ = connect.HTTPServer.prototype;
/**
* Initialize the server.
*
* @param {Array} middleware
* @api private
*/
app.init = function(middleware){
var self = this;
this.cache = {};
this.settings = {};
this.redirects = {};
this.isCallbacks = {};
this._locals = {};
this.dynamicViewHelpers = {};
this.errorHandlers = [];
this.set('env', process.env.NODE_ENV || 'development');
// expose objects to each other
this.use(function(req, res, next){
req.query = req.query || {};
res.setHeader('X-Powered-By', 'Express');
req.app = res.app = self;
req.res = res;
res.req = req;
req.next = next;
// assign req.query
if (req.url.indexOf('?') > 0) {
var query = url.parse(req.url).query;
req.query = qs.parse(query);
}
next();
});
// apply middleware
if (middleware) middleware.forEach(self.use.bind(self));
// router
this.routes = new Router(this);
this.__defineGetter__('router', function(){
this.__usedRouter = true;
return self.routes.middleware;
});
// default locals
this.locals({
settings: this.settings
, app: this
});
// default development configuration
this.configure('development', function(){
this.enable('hints');
});
// default production configuration
this.configure('production', function(){
this.enable('view cache');
});
// register error handlers on "listening"
// so that they disregard definition position.
this.on('listening', this.registerErrorHandlers.bind(this));
// route manipulation methods
methods.forEach(function(method){
self.lookup[method] = function(path){
return self.routes.lookup(method, path);
};
self.match[method] = function(path){
return self.routes.match(method, path);
};
self.remove[method] = function(path){
return self.routes.lookup(method, path).remove();
};
});
// del -> delete
self.lookup.del = self.lookup.delete;
self.match.del = self.match.delete;
self.remove.del = self.remove.delete;
};
/**
* Remove routes matching the given `path`.
*
* @param {Route} path
* @return {Boolean}
* @api public
*/
app.remove = function(path){
return this.routes.lookup('all', path).remove();
};
/**
* Lookup routes defined with a path
* equivalent to `path`.
*
* @param {Stirng} path
* @return {Array}
* @api public
*/
app.lookup = function(path){
return this.routes.lookup('all', path);
};
/**
* Lookup routes matching the given `url`.
*
* @param {Stirng} url
* @return {Array}
* @api public
*/
app.match = function(url){
return this.routes.match('all', url);
};
/**
* When using the vhost() middleware register error handlers.
*/
app.onvhost = function(){
this.registerErrorHandlers();
};
/**
* Register error handlers.
*
* @return {Server} for chaining
* @api public
*/
app.registerErrorHandlers = function(){
this.errorHandlers.forEach(function(fn){
this.use(function(err, req, res, next){
fn.apply(this, arguments);
});
}, this);
return this;
};
/**
* Proxy `connect.HTTPServer#use()` to apply settings to
* mounted applications.
*
* @param {String|Function|Server} route
* @param {Function|Server} middleware
* @return {Server} for chaining
* @api public
*/
app.use = function(route, middleware){
var app, base, handle;
if ('string' != typeof route) {
middleware = route, route = '/';
}
// express app
if (middleware.handle && middleware.set) app = middleware;
// restore .app property on req and res
if (app) {
app.route = route;
middleware = function(req, res, next) {
var orig = req.app;
app.handle(req, res, function(err){
req.app = res.app = orig;
next(err);
});
};
}
connect.HTTPServer.prototype.use.call(this, route, middleware);
// mounted an app, invoke the hook
// and adjust some settings
if (app) {
base = this.set('basepath') || this.route;
if ('/' == base) base = '';
base = base + (app.set('basepath') || app.route);
app.set('basepath', base);
app.parent = this;
if (app.__mounted) app.__mounted.call(app, this);
}
return this;
};
/**
* Assign a callback `fn` which is called
* when this `Server` is passed to `Server#use()`.
*
* Examples:
*
* var app = express.createServer()
* , blog = express.createServer();
*
* blog.mounted(function(parent){
* // parent is app
* // "this" is blog
* });
*
* app.use(blog);
*
* @param {Function} fn
* @return {Server} for chaining
* @api public
*/
app.mounted = function(fn){
this.__mounted = fn;
return this;
};
/**
* See: view.register.
*
* @return {Server} for chaining
* @api public
*/
app.register = function(){
view.register.apply(this, arguments);
return this;
};
/**
* Register the given view helpers `obj`. This method
* can be called several times to apply additional helpers.
*
* @param {Object} obj
* @return {Server} for chaining
* @api public
*/
app.helpers =
app.locals = function(obj){
utils.merge(this._locals, obj);
return this;
};
/**
* Register the given dynamic view helpers `obj`. This method
* can be called several times to apply additional helpers.
*
* @param {Object} obj
* @return {Server} for chaining
* @api public
*/
app.dynamicHelpers = function(obj){
utils.merge(this.dynamicViewHelpers, obj);
return this;
};
/**
* Map the given param placeholder `name`(s) to the given callback(s).
*
* Param mapping is used to provide pre-conditions to routes
* which us normalized placeholders. This callback has the same
* signature as regular middleware, for example below when ":userId"
* is used this function will be invoked in an attempt to load the user.
*
* app.param('userId', function(req, res, next, id){
* User.find(id, function(err, user){
* if (err) {
* next(err);
* } else if (user) {
* req.user = user;
* next();
* } else {
* next(new Error('failed to load user'));
* }
* });
* });
*
* Passing a single function allows you to map logic
* to the values passed to `app.param()`, for example
* this is useful to provide coercion support in a concise manner.
*
* The following example maps regular expressions to param values
* ensuring that they match, otherwise passing control to the next
* route:
*
* app.param(function(name, regexp){
* if (regexp instanceof RegExp) {
* return function(req, res, next, val){
* var captures;
* if (captures = regexp.exec(String(val))) {
* req.params[name] = captures;
* next();
* } else {
* next('route');
* }
* }
* }
* });
*
* We can now use it as shown below, where "/commit/:commit" expects
* that the value for ":commit" is at 5 or more digits. The capture
* groups are then available as `req.params.commit` as we defined
* in the function above.
*
* app.param('commit', /^\d{5,}$/);
*
* For more of this useful functionality take a look
* at [express-params](http://github.com/visionmedia/express-params).
*
* @param {String|Array|Function} name
* @param {Function} fn
* @return {Server} for chaining
* @api public
*/
app.param = function(name, fn){
var self = this
, fns = [].slice.call(arguments, 1);
// array
if (Array.isArray(name)) {
name.forEach(function(name){
fns.forEach(function(fn){
self.param(name, fn);
});
});
// param logic
} else if ('function' == typeof name) {
this.routes.param(name);
// single
} else {
if (':' == name[0]) name = name.substr(1);
fns.forEach(function(fn){
self.routes.param(name, fn);
});
}
return this;
};
/**
* Assign a custom exception handler callback `fn`.
* These handlers are always _last_ in the middleware stack.
*
* @param {Function} fn
* @return {Server} for chaining
* @api public
*/
app.error = function(fn){
this.errorHandlers.push(fn);
return this;
};
/**
* Register the given callback `fn` for the given `type`.
*
* @param {String} type
* @param {Function} fn
* @return {Server} for chaining
* @api public
*/
app.is = function(type, fn){
if (!fn) return this.isCallbacks[type];
this.isCallbacks[type] = fn;
return this;
};
/**
* Assign `setting` to `val`, or return `setting`'s value.
* Mounted servers inherit their parent server's settings.
*
* @param {String} setting
* @param {String} val
* @return {Server|Mixed} for chaining, or the setting value
* @api public
*/
app.set = function(setting, val){
if (val === undefined) {
if (this.settings.hasOwnProperty(setting)) {
return this.settings[setting];
} else if (this.parent) {
return this.parent.set(setting);
}
} else {
this.settings[setting] = val;
return this;
}
};
/**
* Check if `setting` is enabled.
*
* @param {String} setting
* @return {Boolean}
* @api public
*/
app.enabled = function(setting){
return !!this.set(setting);
};
/**
* Check if `setting` is disabled.
*
* @param {String} setting
* @return {Boolean}
* @api public
*/
app.disabled = function(setting){
return !this.set(setting);
};
/**
* Enable `setting`.
*
* @param {String} setting
* @return {Server} for chaining
* @api public
*/
app.enable = function(setting){
return this.set(setting, true);
};
/**
* Disable `setting`.
*
* @param {String} setting
* @return {Server} for chaining
* @api public
*/
app.disable = function(setting){
return this.set(setting, false);
};
/**
* Redirect `key` to `url`.
*
* @param {String} key
* @param {String} url
* @return {Server} for chaining
* @api public
*/
app.redirect = function(key, url){
this.redirects[key] = url;
return this;
};
/**
* Configure callback for zero or more envs,
* when no env is specified that callback will
* be invoked for all environments. Any combination
* can be used multiple times, in any order desired.
*
* Examples:
*
* app.configure(function(){
* // executed for all envs
* });
*
* app.configure('stage', function(){
* // executed staging env
* });
*
* app.configure('stage', 'production', function(){
* // executed for stage and production
* });
*
* @param {String} env...
* @param {Function} fn
* @return {Server} for chaining
* @api public
*/
app.configure = function(env, fn){
var envs = 'all'
, args = toArray(arguments);
fn = args.pop();
if (args.length) envs = args;
if ('all' == envs || ~envs.indexOf(this.settings.env)) fn.call(this);
return this;
};
/**
* Delegate `.VERB(...)` calls to `.route(VERB, ...)`.
*/
methods.forEach(function(method){
app[method] = function(path){
if (1 == arguments.length) return this.routes.lookup(method, path);
var args = [method].concat(toArray(arguments));
if (!this.__usedRouter) this.use(this.router);
return this.routes._route.apply(this.routes, args);
}
});
/**
* Special-cased "all" method, applying the given route `path`,
* middleware, and callback to _every_ HTTP method.
*
* @param {String} path
* @param {Function} ...
* @return {Server} for chaining
* @api public
*/
app.all = function(path){
var args = arguments;
if (1 == args.length) return this.routes.lookup('all', path);
methods.forEach(function(method){
if ('all' == method || 'del' == method) return;
app[method].apply(this, args);
}, this);
return this;
};
// del -> delete alias
app.del = app.delete;

52
example/node/node_modules/express/lib/https.js generated vendored Normal file
View file

@ -0,0 +1,52 @@
/*!
* Express - HTTPSServer
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var connect = require('connect')
, HTTPServer = require('./http')
, https = require('https');
/**
* Expose `HTTPSServer`.
*/
exports = module.exports = HTTPSServer;
/**
* Server proto.
*/
var app = HTTPSServer.prototype;
/**
* Initialize a new `HTTPSServer` with the
* given `options`, and optional `middleware`.
*
* @param {Object} options
* @param {Array} middleware
* @api public
*/
function HTTPSServer(options, middleware){
connect.HTTPSServer.call(this, options, []);
this.init(middleware);
};
/**
* Inherit from `connect.HTTPSServer`.
*/
app.__proto__ = connect.HTTPSServer.prototype;
// mixin HTTPServer methods
Object.keys(HTTPServer.prototype).forEach(function(method){
app[method] = HTTPServer.prototype[method];
});

323
example/node/node_modules/express/lib/request.js generated vendored Normal file
View file

@ -0,0 +1,323 @@
/*!
* Express - request
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var http = require('http')
, req = http.IncomingMessage.prototype
, utils = require('./utils')
, parse = require('url').parse
, mime = require('mime');
/**
* Default flash formatters.
*
* @type Object
*/
var flashFormatters = exports.flashFormatters = {
s: function(val){
return String(val);
}
};
/**
* Return request header or optional default.
*
* The `Referrer` header field is special-cased,
* both `Referrer` and `Referer` will yield are
* interchangeable.
*
* Examples:
*
* req.header('Content-Type');
* // => "text/plain"
*
* req.header('content-type');
* // => "text/plain"
*
* req.header('Accept');
* // => undefined
*
* req.header('Accept', 'text/html');
* // => "text/html"
*
* @param {String} name
* @param {String} defaultValue
* @return {String}
* @api public
*/
req.header = function(name, defaultValue){
switch (name = name.toLowerCase()) {
case 'referer':
case 'referrer':
return this.headers.referrer
|| this.headers.referer
|| defaultValue;
default:
return this.headers[name] || defaultValue;
}
};
/**
* Get `field`'s `param` value, defaulting to ''.
*
* Examples:
*
* req.get('content-disposition', 'filename');
* // => "something.png"
*
* @param {String} field
* @param {String} param
* @return {String}
* @api public
*/
req.get = function(field, param){
var val = this.header(field);
if (!val) return '';
var regexp = new RegExp(param + ' *= *(?:"([^"]+)"|([^;]+))', 'i');
if (!regexp.exec(val)) return '';
return RegExp.$1 || RegExp.$2;
};
/**
* Short-hand for `require('url').parse(req.url).pathname`.
*
* @return {String}
* @api public
*/
req.__defineGetter__('path', function(){
return parse(this.url).pathname;
});
/**
* Check if the _Accept_ header is present, and includes the given `type`.
*
* When the _Accept_ header is not present `true` is returned. Otherwise
* the given `type` is matched by an exact match, and then subtypes. You
* may pass the subtype such as "html" which is then converted internally
* to "text/html" using the mime lookup table.
*
* Examples:
*
* // Accept: text/html
* req.accepts('html');
* // => true
*
* // Accept: text/*; application/json
* req.accepts('html');
* req.accepts('text/html');
* req.accepts('text/plain');
* req.accepts('application/json');
* // => true
*
* req.accepts('image/png');
* req.accepts('png');
* // => false
*
* @param {String} type
* @return {Boolean}
* @api public
*/
req.accepts = function(type){
var accept = this.header('Accept');
// normalize extensions ".json" -> "json"
if (type && '.' == type[0]) type = type.substr(1);
// when Accept does not exist, or is '*/*' return true
if (!accept || '*/*' == accept) {
return true;
} else if (type) {
// allow "html" vs "text/html" etc
if (!~type.indexOf('/')) type = mime.lookup(type);
// check if we have a direct match
if (~accept.indexOf(type)) return true;
// check if we have type/*
type = type.split('/')[0] + '/*';
return !!~accept.indexOf(type);
} else {
return false;
}
};
/**
* Return the value of param `name` when present or `defaultValue`.
*
* - Checks route placeholders, ex: _/user/:id_
* - Checks query string params, ex: ?id=12
* - Checks urlencoded body params, ex: id=12
*
* To utilize urlencoded request bodies, `req.body`
* should be an object. This can be done by using
* the `connect.bodyParser` middleware.
*
* @param {String} name
* @param {Mixed} defaultValue
* @return {String}
* @api public
*/
req.param = function(name, defaultValue){
// route params like /user/:id
if (this.params && this.params.hasOwnProperty(name) && undefined !== this.params[name]) {
return this.params[name];
}
// query string params
if (undefined !== this.query[name]) {
return this.query[name];
}
// request body params via connect.bodyParser
if (this.body && undefined !== this.body[name]) {
return this.body[name];
}
return defaultValue;
};
/**
* Queue flash `msg` of the given `type`.
*
* Examples:
*
* req.flash('info', 'email sent');
* req.flash('error', 'email delivery failed');
* req.flash('info', 'email re-sent');
* // => 2
*
* req.flash('info');
* // => ['email sent', 'email re-sent']
*
* req.flash('info');
* // => []
*
* req.flash();
* // => { error: ['email delivery failed'], info: [] }
*
* Formatting:
*
* Flash notifications also support arbitrary formatting support.
* For example you may pass variable arguments to `req.flash()`
* and use the %s specifier to be replaced by the associated argument:
*
* req.flash('info', 'email has been sent to %s.', userName);
*
* To add custom formatters use the `exports.flashFormatters` object.
*
* @param {String} type
* @param {String} msg
* @return {Array|Object|Number}
* @api public
*/
req.flash = function(type, msg){
if (this.session === undefined) throw Error('req.flash() requires sessions');
var msgs = this.session.flash = this.session.flash || {};
if (type && msg) {
var i = 2
, args = arguments
, formatters = this.app.flashFormatters || {};
formatters.__proto__ = flashFormatters;
msg = utils.miniMarkdown(msg);
msg = msg.replace(/%([a-zA-Z])/g, function(_, format){
var formatter = formatters[format];
if (formatter) return formatter(utils.escape(args[i++]));
});
return (msgs[type] = msgs[type] || []).push(msg);
} else if (type) {
var arr = msgs[type];
delete msgs[type];
return arr || [];
} else {
this.session.flash = {};
return msgs;
}
};
/**
* Check if the incoming request contains the "Content-Type"
* header field, and it contains the give mime `type`.
*
* Examples:
*
* // With Content-Type: text/html; charset=utf-8
* req.is('html');
* req.is('text/html');
* // => true
*
* // When Content-Type is application/json
* req.is('json');
* req.is('application/json');
* // => true
*
* req.is('html');
* // => false
*
* Ad-hoc callbacks can also be registered with Express, to perform
* assertions again the request, for example if we need an expressive
* way to check if our incoming request is an image, we can register "an image"
* callback:
*
* app.is('an image', function(req){
* return 0 == req.headers['content-type'].indexOf('image');
* });
*
* Now within our route callbacks, we can use to to assert content types
* such as "image/jpeg", "image/png", etc.
*
* app.post('/image/upload', function(req, res, next){
* if (req.is('an image')) {
* // do something
* } else {
* next();
* }
* });
*
* @param {String} type
* @return {Boolean}
* @api public
*/
req.is = function(type){
var fn = this.app.is(type);
if (fn) return fn(this);
var ct = this.headers['content-type'];
if (!ct) return false;
ct = ct.split(';')[0];
if (!~type.indexOf('/')) type = mime.lookup(type);
if (~type.indexOf('*')) {
type = type.split('/');
ct = ct.split('/');
if ('*' == type[0] && type[1] == ct[1]) return true;
if ('*' == type[1] && type[0] == ct[0]) return true;
return false;
}
return !! ~ct.indexOf(type);
};
// Callback for isXMLHttpRequest / xhr
function isxhr() {
return this.header('X-Requested-With', '').toLowerCase() === 'xmlhttprequest';
}
/**
* Check if the request was an _XMLHttpRequest_.
*
* @return {Boolean}
* @api public
*/
req.__defineGetter__('isXMLHttpRequest', isxhr);
req.__defineGetter__('xhr', isxhr);

460
example/node/node_modules/express/lib/response.js generated vendored Normal file
View file

@ -0,0 +1,460 @@
/*!
* Express - response
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var fs = require('fs')
, http = require('http')
, path = require('path')
, connect = require('connect')
, utils = connect.utils
, parseRange = require('./utils').parseRange
, res = http.ServerResponse.prototype
, send = connect.static.send
, mime = require('mime')
, basename = path.basename
, join = path.join;
/**
* Send a response with the given `body` and optional `headers` and `status` code.
*
* Examples:
*
* res.send();
* res.send(new Buffer('wahoo'));
* res.send({ some: 'json' });
* res.send('<p>some html</p>');
* res.send('Sorry, cant find that', 404);
* res.send('text', { 'Content-Type': 'text/plain' }, 201);
* res.send(404);
*
* @param {String|Object|Number|Buffer} body or status
* @param {Object|Number} headers or status
* @param {Number} status
* @return {ServerResponse}
* @api public
*/
res.send = function(body, headers, status){
// allow status as second arg
if ('number' == typeof headers) {
status = headers,
headers = null;
}
// default status
status = status || this.statusCode;
// allow 0 args as 204
if (!arguments.length || undefined === body) status = 204;
// determine content type
switch (typeof body) {
case 'number':
if (!this.header('Content-Type')) {
this.contentType('.txt');
}
body = http.STATUS_CODES[status = body];
break;
case 'string':
if (!this.header('Content-Type')) {
this.charset = this.charset || 'utf-8';
this.contentType('.html');
}
break;
case 'boolean':
case 'object':
if (Buffer.isBuffer(body)) {
if (!this.header('Content-Type')) {
this.contentType('.bin');
}
} else {
return this.json(body, headers, status);
}
break;
}
// populate Content-Length
if (undefined !== body && !this.header('Content-Length')) {
this.header('Content-Length', Buffer.isBuffer(body)
? body.length
: Buffer.byteLength(body));
}
// merge headers passed
if (headers) {
var fields = Object.keys(headers);
for (var i = 0, len = fields.length; i < len; ++i) {
var field = fields[i];
this.header(field, headers[field]);
}
}
// strip irrelevant headers
if (204 == status || 304 == status) {
this.removeHeader('Content-Type');
this.removeHeader('Content-Length');
body = '';
}
// respond
this.statusCode = status;
this.end('HEAD' == this.req.method ? null : body);
return this;
};
/**
* Send JSON response with `obj`, optional `headers`, and optional `status`.
*
* Examples:
*
* res.json(null);
* res.json({ user: 'tj' });
* res.json('oh noes!', 500);
* res.json('I dont have that', 404);
*
* @param {Mixed} obj
* @param {Object|Number} headers or status
* @param {Number} status
* @return {ServerResponse}
* @api public
*/
res.json = function(obj, headers, status){
var body = JSON.stringify(obj)
, callback = this.req.query.callback
, jsonp = this.app.enabled('jsonp callback');
this.charset = this.charset || 'utf-8';
this.header('Content-Type', 'application/json');
if (callback && jsonp) {
this.header('Content-Type', 'text/javascript');
body = callback.replace(/[^\w$.]/g, '') + '(' + body + ');';
}
return this.send(body, headers, status);
};
/**
* Set status `code`.
*
* @param {Number} code
* @return {ServerResponse}
* @api public
*/
res.status = function(code){
this.statusCode = code;
return this;
};
/**
* Transfer the file at the given `path`. Automatically sets
* the _Content-Type_ response header field. `next()` is called
* when `path` is a directory, or when an error occurs.
*
* Options:
*
* - `maxAge` defaulting to 0
* - `root` root directory for relative filenames
*
* @param {String} path
* @param {Object|Function} options or fn
* @param {Function} fn
* @api public
*/
res.sendfile = function(path, options, fn){
var next = this.req.next;
options = options || {};
// support function as second arg
if ('function' == typeof options) {
fn = options;
options = {};
}
options.path = encodeURIComponent(path);
options.callback = fn;
send(this.req, this, next, options);
};
/**
* Set _Content-Type_ response header passed through `mime.lookup()`.
*
* Examples:
*
* var filename = 'path/to/image.png';
* res.contentType(filename);
* // res.headers['Content-Type'] is now "image/png"
*
* res.contentType('.html');
* res.contentType('html');
* res.contentType('json');
* res.contentType('png');
*
* @param {String} type
* @return {String} the resolved mime type
* @api public
*/
res.contentType = function(type){
return this.header('Content-Type', mime.lookup(type));
};
/**
* Set _Content-Disposition_ header to _attachment_ with optional `filename`.
*
* @param {String} filename
* @return {ServerResponse}
* @api public
*/
res.attachment = function(filename){
if (filename) this.contentType(filename);
this.header('Content-Disposition', filename
? 'attachment; filename="' + basename(filename) + '"'
: 'attachment');
return this;
};
/**
* Transfer the file at the given `path`, with optional
* `filename` as an attachment and optional callback `fn(err)`,
* and optional `fn2(err)` which is invoked when an error has
* occurred after header has been sent.
*
* @param {String} path
* @param {String|Function} filename or fn
* @param {Function} fn
* @param {Function} fn2
* @api public
*/
res.download = function(path, filename, fn, fn2){
var self = this;
// support callback as second arg
if ('function' == typeof filename) {
fn2 = fn;
fn = filename;
filename = null;
}
// transfer the file
this.attachment(filename || path).sendfile(path, function(err){
var sentHeader = self._header;
if (err) {
if (!sentHeader) self.removeHeader('Content-Disposition');
if (sentHeader) {
fn2 && fn2(err);
} else if (fn) {
fn(err);
} else {
self.req.next(err);
}
} else if (fn) {
fn();
}
});
};
/**
* Set or get response header `name` with optional `val`.
*
* @param {String} name
* @param {String} val
* @return {ServerResponse} for chaining
* @api public
*/
res.header = function(name, val){
if (1 == arguments.length) return this.getHeader(name);
this.setHeader(name, val);
return this;
};
/**
* Clear cookie `name`.
*
* @param {String} name
* @param {Object} options
* @api public
*/
res.clearCookie = function(name, options){
var opts = { expires: new Date(1) };
this.cookie(name, '', options
? utils.merge(options, opts)
: opts);
};
/**
* Set cookie `name` to `val`, with the given `options`.
*
* Options:
*
* - `maxAge` max-age in milliseconds, converted to `expires`
* - `path` defaults to the "basepath" setting which is typically "/"
*
* Examples:
*
* // "Remember Me" for 15 minutes
* res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
*
* // save as above
* res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
*
* @param {String} name
* @param {String} val
* @param {Options} options
* @api public
*/
res.cookie = function(name, val, options){
options = options || {};
if ('maxAge' in options) options.expires = new Date(Date.now() + options.maxAge);
if (undefined === options.path) options.path = this.app.set('basepath');
var cookie = utils.serializeCookie(name, val, options);
this.header('Set-Cookie', cookie);
};
/**
* Redirect to the given `url` with optional response `status`
* defauling to 302.
*
* The given `url` can also be the name of a mapped url, for
* example by default express supports "back" which redirects
* to the _Referrer_ or _Referer_ headers or the application's
* "basepath" setting. Express also supports "basepath" out of the box,
* which can be set via `app.set('basepath', '/blog');`, and defaults
* to '/'.
*
* Redirect Mapping:
*
* To extend the redirect mapping capabilities that Express provides,
* we may use the `app.redirect()` method:
*
* app.redirect('google', 'http://google.com');
*
* Now in a route we may call:
*
* res.redirect('google');
*
* We may also map dynamic redirects:
*
* app.redirect('comments', function(req, res){
* return '/post/' + req.params.id + '/comments';
* });
*
* So now we may do the following, and the redirect will dynamically adjust to
* the context of the request. If we called this route with _GET /post/12_ our
* redirect _Location_ would be _/post/12/comments_.
*
* app.get('/post/:id', function(req, res){
* res.redirect('comments');
* });
*
* Unless an absolute `url` is given, the app's mount-point
* will be respected. For example if we redirect to `/posts`,
* and our app is mounted at `/blog` we will redirect to `/blog/posts`.
*
* @param {String} url
* @param {Number} code
* @api public
*/
res.redirect = function(url, status){
var app = this.app
, req = this.req
, base = app.set('basepath') || app.route
, status = status || 302
, head = 'HEAD' == req.method
, body;
// Setup redirect map
var map = {
back: req.header('Referrer', base)
, home: base
};
// Support custom redirect map
map.__proto__ = app.redirects;
// Attempt mapped redirect
var mapped = 'function' == typeof map[url]
? map[url](req, this)
: map[url];
// Perform redirect
url = mapped || url;
// Relative
if (!~url.indexOf('://')) {
// Respect mount-point
if ('/' != base && 0 != url.indexOf(base)) url = base + url;
// Absolute
var host = req.headers.host
, tls = req.connection.encrypted;
url = 'http' + (tls ? 's' : '') + '://' + host + url;
}
// Support text/{plain,html} by default
if (req.accepts('html')) {
body = '<p>' + http.STATUS_CODES[status] + '. Redirecting to <a href="' + url + '">' + url + '</a></p>';
this.header('Content-Type', 'text/html');
} else {
body = http.STATUS_CODES[status] + '. Redirecting to ' + url;
this.header('Content-Type', 'text/plain');
}
// Respond
this.statusCode = status;
this.header('Location', url);
this.end(head ? null : body);
};
/**
* Assign the view local variable `name` to `val` or return the
* local previously assigned to `name`.
*
* @param {String} name
* @param {Mixed} val
* @return {Mixed} val
* @api public
*/
res.local = function(name, val){
this._locals = this._locals || {};
return undefined === val
? this._locals[name]
: this._locals[name] = val;
};
/**
* Assign several locals with the given `obj`,
* or return the locals.
*
* @param {Object} obj
* @return {Object|Undefined}
* @api public
*/
res.locals =
res.helpers = function(obj){
if (obj) {
for (var key in obj) {
this.local(key, obj[key]);
}
} else {
return this._locals;
}
};

View file

@ -0,0 +1,53 @@
/*!
* Express - router - Collection
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Expose `Collection`.
*/
module.exports = Collection;
/**
* Initialize a new route `Collection`
* with the given `router`.
*
* @param {Router} router
* @api private
*/
function Collection(router) {
Array.apply(this, arguments);
this.router = router;
}
/**
* Inherit from `Array.prototype`.
*/
Collection.prototype.__proto__ = Array.prototype;
/**
* Remove the routes in this collection.
*
* @return {Collection} of routes removed
* @api public
*/
Collection.prototype.remove = function(){
var router = this.router
, len = this.length
, ret = new Collection(this.router);
for (var i = 0; i < len; ++i) {
if (router.remove(this[i])) {
ret.push(this[i]);
}
}
return ret;
};

398
example/node/node_modules/express/lib/router/index.js generated vendored Normal file
View file

@ -0,0 +1,398 @@
/*!
* Express - Router
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var Route = require('./route')
, Collection = require('./collection')
, utils = require('../utils')
, parse = require('url').parse
, toArray = utils.toArray;
/**
* Expose `Router` constructor.
*/
exports = module.exports = Router;
/**
* Expose HTTP methods.
*/
var methods = exports.methods = require('./methods');
/**
* Initialize a new `Router` with the given `app`.
*
* @param {express.HTTPServer} app
* @api private
*/
function Router(app) {
var self = this;
this.app = app;
this.routes = {};
this.params = {};
this._params = [];
this.middleware = function(req, res, next){
self._dispatch(req, res, next);
};
}
/**
* Register a param callback `fn` for the given `name`.
*
* @param {String|Function} name
* @param {Function} fn
* @return {Router} for chaining
* @api public
*/
Router.prototype.param = function(name, fn){
// param logic
if ('function' == typeof name) {
this._params.push(name);
return;
}
// apply param functions
var params = this._params
, len = params.length
, ret;
for (var i = 0; i < len; ++i) {
if (ret = params[i](name, fn)) {
fn = ret;
}
}
// ensure we end up with a
// middleware function
if ('function' != typeof fn) {
throw new Error('invalid param() call for ' + name + ', got ' + fn);
}
(this.params[name] = this.params[name] || []).push(fn);
return this;
};
/**
* Return a `Collection` of all routes defined.
*
* @return {Collection}
* @api public
*/
Router.prototype.all = function(){
return this.find(function(){
return true;
});
};
/**
* Remove the given `route`, returns
* a bool indicating if the route was present
* or not.
*
* @param {Route} route
* @return {Boolean}
* @api public
*/
Router.prototype.remove = function(route){
var routes = this.routes[route.method]
, len = routes.length;
for (var i = 0; i < len; ++i) {
if (route == routes[i]) {
routes.splice(i, 1);
return true;
}
}
};
/**
* Return routes with route paths matching `path`.
*
* @param {String} method
* @param {String} path
* @return {Collection}
* @api public
*/
Router.prototype.lookup = function(method, path){
return this.find(function(route){
return path == route.path
&& (route.method == method
|| method == 'all');
});
};
/**
* Return routes with regexps that match the given `url`.
*
* @param {String} method
* @param {String} url
* @return {Collection}
* @api public
*/
Router.prototype.match = function(method, url){
return this.find(function(route){
return route.match(url)
&& (route.method == method
|| method == 'all');
});
};
/**
* Find routes based on the return value of `fn`
* which is invoked once per route.
*
* @param {Function} fn
* @return {Collection}
* @api public
*/
Router.prototype.find = function(fn){
var len = methods.length
, ret = new Collection(this)
, method
, routes
, route;
for (var i = 0; i < len; ++i) {
method = methods[i];
routes = this.routes[method];
if (!routes) continue;
for (var j = 0, jlen = routes.length; j < jlen; ++j) {
route = routes[j];
if (fn(route)) ret.push(route);
}
}
return ret;
};
/**
* Route dispatcher aka the route "middleware".
*
* @param {IncomingMessage} req
* @param {ServerResponse} res
* @param {Function} next
* @api private
*/
Router.prototype._dispatch = function(req, res, next){
var params = this.params
, self = this;
// route dispatch
(function pass(i, err){
var paramCallbacks
, paramIndex = 0
, paramVal
, route
, keys
, key
, ret;
// match next route
function nextRoute(err) {
pass(req._route_index + 1, err);
}
// match route
req.route = route = self._match(req, i);
// implied OPTIONS
if (!route && 'OPTIONS' == req.method) return self._options(req, res);
// no route
if (!route) return next(err);
// we have a route
// start at param 0
req.params = route.params;
keys = route.keys;
i = 0;
// param callbacks
function param(err) {
paramIndex = 0;
key = keys[i++];
paramVal = key && req.params[key.name];
paramCallbacks = key && params[key.name];
try {
if ('route' == err) {
nextRoute();
} else if (err) {
i = 0;
callbacks(err);
} else if (paramCallbacks && undefined !== paramVal) {
paramCallback();
} else if (key) {
param();
} else {
i = 0;
callbacks();
}
} catch (err) {
param(err);
}
};
param(err);
// single param callbacks
function paramCallback(err) {
var fn = paramCallbacks[paramIndex++];
if (err || !fn) return param(err);
fn(req, res, paramCallback, paramVal, key.name);
}
// invoke route callbacks
function callbacks(err) {
var fn = route.callbacks[i++];
try {
if ('route' == err) {
nextRoute();
} else if (err && fn) {
if (fn.length < 4) return callbacks(err);
fn(err, req, res, callbacks);
} else if (fn) {
fn(req, res, callbacks);
} else {
nextRoute(err);
}
} catch (err) {
callbacks(err);
}
}
})(0);
};
/**
* Respond to __OPTIONS__ method.
*
* @param {IncomingMessage} req
* @param {ServerResponse} res
* @api private
*/
Router.prototype._options = function(req, res){
var path = parse(req.url).pathname
, body = this._optionsFor(path).join(',');
res.send(body, { Allow: body });
};
/**
* Return an array of HTTP verbs or "options" for `path`.
*
* @param {String} path
* @return {Array}
* @api private
*/
Router.prototype._optionsFor = function(path){
var self = this;
return methods.filter(function(method){
var routes = self.routes[method];
if (!routes || 'options' == method) return;
for (var i = 0, len = routes.length; i < len; ++i) {
if (routes[i].match(path)) return true;
}
}).map(function(method){
return method.toUpperCase();
});
};
/**
* Attempt to match a route for `req`
* starting from offset `i`.
*
* @param {IncomingMessage} req
* @param {Number} i
* @return {Route}
* @api private
*/
Router.prototype._match = function(req, i){
var method = req.method.toLowerCase()
, url = parse(req.url)
, path = url.pathname
, routes = this.routes
, captures
, route
, keys;
// pass HEAD to GET routes
if ('head' == method) method = 'get';
// routes for this method
if (routes = routes[method]) {
// matching routes
for (var len = routes.length; i < len; ++i) {
route = routes[i];
if (captures = route.match(path)) {
keys = route.keys;
route.params = [];
// params from capture groups
for (var j = 1, jlen = captures.length; j < jlen; ++j) {
var key = keys[j-1]
, val = 'string' == typeof captures[j]
? decodeURIComponent(captures[j])
: captures[j];
if (key) {
route.params[key.name] = val;
} else {
route.params.push(val);
}
}
// all done
req._route_index = i;
return route;
}
}
}
};
/**
* Route `method`, `path`, and one or more callbacks.
*
* @param {String} method
* @param {String} path
* @param {Function} callback...
* @return {Router} for chaining
* @api private
*/
Router.prototype._route = function(method, path, callbacks){
var app = this.app
, callbacks = utils.flatten(toArray(arguments, 2));
// ensure path was given
if (!path) throw new Error('app.' + method + '() requires a path');
// create the route
var route = new Route(method, path, callbacks, {
sensitive: app.enabled('case sensitive routes')
, strict: app.enabled('strict routing')
});
// add it
(this.routes[method] = this.routes[method] || [])
.push(route);
return this;
};

View file

@ -0,0 +1,70 @@
/*!
* Express - router - methods
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Hypertext Transfer Protocol -- HTTP/1.1
* http://www.ietf.org/rfc/rfc2616.txt
*/
var RFC2616 = ['OPTIONS', 'GET', 'POST', 'PUT', 'DELETE', 'TRACE', 'CONNECT'];
/**
* HTTP Extensions for Distributed Authoring -- WEBDAV
* http://www.ietf.org/rfc/rfc2518.txt
*/
var RFC2518 = ['PROPFIND', 'PROPPATCH', 'MKCOL', 'COPY', 'MOVE', 'LOCK', 'UNLOCK'];
/**
* Versioning Extensions to WebDAV
* http://www.ietf.org/rfc/rfc3253.txt
*/
var RFC3253 = ['VERSION-CONTROL', 'REPORT', 'CHECKOUT', 'CHECKIN', 'UNCHECKOUT', 'MKWORKSPACE', 'UPDATE', 'LABEL', 'MERGE', 'BASELINE-CONTROL', 'MKACTIVITY'];
/**
* Ordered Collections Protocol (WebDAV)
* http://www.ietf.org/rfc/rfc3648.txt
*/
var RFC3648 = ['ORDERPATCH'];
/**
* Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol
* http://www.ietf.org/rfc/rfc3744.txt
*/
var RFC3744 = ['ACL'];
/**
* Web Distributed Authoring and Versioning (WebDAV) SEARCH
* http://www.ietf.org/rfc/rfc5323.txt
*/
var RFC5323 = ['SEARCH'];
/**
* PATCH Method for HTTP
* http://www.ietf.org/rfc/rfc5789.txt
*/
var RFC5789 = ['PATCH'];
/**
* Expose the methods.
*/
module.exports = [].concat(
RFC2616
, RFC2518
, RFC3253
, RFC3648
, RFC3744
, RFC5323
, RFC5789).map(function(method){
return method.toLowerCase();
});

88
example/node/node_modules/express/lib/router/route.js generated vendored Normal file
View file

@ -0,0 +1,88 @@
/*!
* Express - router - Route
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Expose `Route`.
*/
module.exports = Route;
/**
* Initialize `Route` with the given HTTP `method`, `path`,
* and an array of `callbacks` and `options`.
*
* Options:
*
* - `sensitive` enable case-sensitive routes
* - `strict` enable strict matching for trailing slashes
*
* @param {String} method
* @param {String} path
* @param {Array} callbacks
* @param {Object} options.
* @api private
*/
function Route(method, path, callbacks, options) {
options = options || {};
this.path = path;
this.method = method;
this.callbacks = callbacks;
this.regexp = normalize(path
, this.keys = []
, options.sensitive
, options.strict);
}
/**
* Check if this route matches `path` and return captures made.
*
* @param {String} path
* @return {Array}
* @api private
*/
Route.prototype.match = function(path){
return this.regexp.exec(path);
};
/**
* Normalize the given path string,
* returning a regular expression.
*
* An empty array should be passed,
* which will contain the placeholder
* key names. For example "/user/:id" will
* then contain ["id"].
*
* @param {String|RegExp} path
* @param {Array} keys
* @param {Boolean} sensitive
* @param {Boolean} strict
* @return {RegExp}
* @api private
*/
function normalize(path, keys, sensitive, strict) {
if (path instanceof RegExp) return path;
path = path
.concat(strict ? '' : '/?')
.replace(/\/\(/g, '(?:/')
.replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional){
keys.push({ name: key, optional: !! optional });
slash = slash || '';
return ''
+ (optional ? '' : slash)
+ '(?:'
+ (optional ? slash : '')
+ (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')'
+ (optional || '');
})
.replace(/([\/.])/g, '\\$1')
.replace(/\*/g, '(.*)');
return new RegExp('^' + path + '$', sensitive ? '' : 'i');
}

152
example/node/node_modules/express/lib/utils.js generated vendored Normal file
View file

@ -0,0 +1,152 @@
/*!
* Express - Utils
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Check if `path` looks absolute.
*
* @param {String} path
* @return {Boolean}
* @api private
*/
exports.isAbsolute = function(path){
if ('/' == path[0]) return true;
if (':' == path[1] && '\\' == path[2]) return true;
};
/**
* Merge object `b` with `a` giving precedence to
* values in object `a`.
*
* @param {Object} a
* @param {Object} b
* @return {Object} a
* @api private
*/
exports.union = function(a, b){
if (a && b) {
var keys = Object.keys(b)
, len = keys.length
, key;
for (var i = 0; i < len; ++i) {
key = keys[i];
if (!a.hasOwnProperty(key)) {
a[key] = b[key];
}
}
}
return a;
};
/**
* Flatten the given `arr`.
*
* @param {Array} arr
* @return {Array}
* @api private
*/
exports.flatten = function(arr, ret){
var ret = ret || []
, len = arr.length;
for (var i = 0; i < len; ++i) {
if (Array.isArray(arr[i])) {
exports.flatten(arr[i], ret);
} else {
ret.push(arr[i]);
}
}
return ret;
};
/**
* Parse mini markdown implementation.
* The following conversions are supported,
* primarily for the "flash" middleware:
*
* _foo_ or *foo* become <em>foo</em>
* __foo__ or **foo** become <strong>foo</strong>
* [A](B) becomes <a href="B">A</a>
*
* @param {String} str
* @return {String}
* @api private
*/
exports.miniMarkdown = function(str){
return String(str)
.replace(/(__|\*\*)(.*?)\1/g, '<strong>$2</strong>')
.replace(/(_|\*)(.*?)\1/g, '<em>$2</em>')
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
};
/**
* Escape special characters in the given string of html.
*
* @param {String} html
* @return {String}
* @api private
*/
exports.escape = function(html) {
return String(html)
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
};
/**
* Parse "Range" header `str` relative to the given file `size`.
*
* @param {Number} size
* @param {String} str
* @return {Array}
* @api private
*/
exports.parseRange = function(size, str){
var valid = true;
var arr = str.substr(6).split(',').map(function(range){
var range = range.split('-')
, start = parseInt(range[0], 10)
, end = parseInt(range[1], 10);
// -500
if (isNaN(start)) {
start = size - end;
end = size - 1;
// 500-
} else if (isNaN(end)) {
end = size - 1;
}
// Invalid
if (isNaN(start) || isNaN(end) || start > end) valid = false;
return { start: start, end: end };
});
return valid ? arr : undefined;
};
/**
* Fast alternative to `Array.prototype.slice.call()`.
*
* @param {Arguments} args
* @param {Number} n
* @return {Array}
* @api public
*/
exports.toArray = function(args, i){
var arr = []
, len = args.length
, i = i || 0;
for (; i < len; ++i) arr.push(args[i]);
return arr;
};

460
example/node/node_modules/express/lib/view.js generated vendored Normal file
View file

@ -0,0 +1,460 @@
/*!
* Express - view
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var path = require('path')
, extname = path.extname
, dirname = path.dirname
, basename = path.basename
, utils = require('connect').utils
, View = require('./view/view')
, partial = require('./view/partial')
, union = require('./utils').union
, merge = utils.merge
, http = require('http')
, res = http.ServerResponse.prototype;
/**
* Expose constructors.
*/
exports = module.exports = View;
/**
* Export template engine registrar.
*/
exports.register = View.register;
/**
* Lookup and compile `view` with cache support by supplying
* both the `cache` object and `cid` string,
* followed by `options` passed to `exports.lookup()`.
*
* @param {String} view
* @param {Object} cache
* @param {Object} cid
* @param {Object} options
* @return {View}
* @api private
*/
exports.compile = function(view, cache, cid, options){
if (cache && cid && cache[cid]){
options.filename = cache[cid].path;
return cache[cid];
}
// lookup
view = exports.lookup(view, options);
// hints
if (!view.exists) {
if (options.hint) hintAtViewPaths(view.original, options);
var err = new Error('failed to locate view "' + view.original.view + '"');
err.view = view.original;
throw err;
}
// compile
options.filename = view.path;
view.fn = view.templateEngine.compile(view.contents, options);
cache[cid] = view;
return view;
};
/**
* Lookup `view`, returning an instanceof `View`.
*
* Options:
*
* - `root` root directory path
* - `defaultEngine` default template engine
* - `parentView` parent `View` object
* - `cache` cache object
* - `cacheid` optional cache id
*
* Lookup:
*
* - partial `_<name>`
* - any `<name>/index`
* - non-layout `../<name>/index`
* - any `<root>/<name>`
* - partial `<root>/_<name>`
*
* @param {String} view
* @param {Object} options
* @return {View}
* @api private
*/
exports.lookup = function(view, options){
var orig = view = new View(view, options)
, partial = options.isPartial
, layout = options.isLayout;
// Try _ prefix ex: ./views/_<name>.jade
// taking precedence over the direct path
if (partial) {
view = new View(orig.prefixPath, options);
if (!view.exists) view = orig;
}
// Try index ex: ./views/user/index.jade
if (!layout && !view.exists) view = new View(orig.indexPath, options);
// Try ../<name>/index ex: ../user/index.jade
// when calling partial('user') within the same dir
if (!layout && !view.exists) view = new View(orig.upIndexPath, options);
// Try root ex: <root>/user.jade
if (!view.exists) view = new View(orig.rootPath, options);
// Try root _ prefix ex: <root>/_user.jade
if (!view.exists && partial) view = new View(view.prefixPath, options);
view.original = orig;
return view;
};
/**
* Partial render helper.
*
* @api private
*/
function renderPartial(res, view, options, parentLocals, parent){
var collection, object, locals;
if (options) {
// collection
if (options.collection) {
collection = options.collection;
delete options.collection;
} else if ('length' in options) {
collection = options;
options = {};
}
// locals
if (options.locals) {
locals = options.locals;
delete options.locals;
}
// object
if ('Object' != options.constructor.name) {
object = options;
options = {};
} else if (undefined != options.object) {
object = options.object;
delete options.object;
}
} else {
options = {};
}
// Inherit locals from parent
union(options, parentLocals);
// Merge locals
if (locals) merge(options, locals);
// Partials dont need layouts
options.isPartial = true;
options.layout = false;
// Deduce name from view path
var name = options.as || partial.resolveObjectName(view);
// Render partial
function render(){
if (object) {
if ('string' == typeof name) {
options[name] = object;
} else if (name === global) {
merge(options, object);
}
}
return res.render(view, options, null, parent, true);
}
// Collection support
if (collection) {
var len = collection.length
, buf = ''
, keys
, key
, val;
options.collectionLength = len;
if ('number' == typeof len || Array.isArray(collection)) {
for (var i = 0; i < len; ++i) {
val = collection[i];
options.firstInCollection = i == 0;
options.indexInCollection = i;
options.lastInCollection = i == len - 1;
object = val;
buf += render();
}
} else {
keys = Object.keys(collection);
len = keys.length;
options.collectionLength = len;
options.collectionKeys = keys;
for (var i = 0; i < len; ++i) {
key = keys[i];
val = collection[key];
options.keyInCollection = key;
options.firstInCollection = i == 0;
options.indexInCollection = i;
options.lastInCollection = i == len - 1;
object = val;
buf += render();
}
}
return buf;
} else {
return render();
}
};
/**
* Render `view` partial with the given `options`. Optionally a
* callback `fn(err, str)` may be passed instead of writing to
* the socket.
*
* Options:
*
* - `object` Single object with name derived from the view (unless `as` is present)
*
* - `as` Variable name for each `collection` value, defaults to the view name.
* * as: 'something' will add the `something` local variable
* * as: this will use the collection value as the template context
* * as: global will merge the collection value's properties with `locals`
*
* - `collection` Array of objects, the name is derived from the view name itself.
* For example _video.html_ will have a object _video_ available to it.
*
* @param {String} view
* @param {Object|Array|Function} options, collection, callback, or object
* @param {Function} fn
* @return {String}
* @api public
*/
res.partial = function(view, options, fn){
var app = this.app
, options = options || {}
, viewEngine = app.set('view engine')
, parent = {};
// accept callback as second argument
if ('function' == typeof options) {
fn = options;
options = {};
}
// root "views" option
parent.dirname = app.set('views') || process.cwd() + '/views';
// utilize "view engine" option
if (viewEngine) parent.engine = viewEngine;
// render the partial
try {
var str = renderPartial(this, view, options, null, parent);
} catch (err) {
if (fn) {
fn(err);
} else {
this.req.next(err);
}
return;
}
// callback or transfer
if (fn) {
fn(null, str);
} else {
this.send(str);
}
};
/**
* Render `view` with the given `options` and optional callback `fn`.
* When a callback function is given a response will _not_ be made
* automatically, however otherwise a response of _200_ and _text/html_ is given.
*
* Options:
*
* - `scope` Template evaluation context (the value of `this`)
* - `debug` Output debugging information
* - `status` Response status code
*
* @param {String} view
* @param {Object|Function} options or callback function
* @param {Function} fn
* @api public
*/
res.render = function(view, opts, fn, parent, sub){
// support callback function as second arg
if ('function' == typeof opts) {
fn = opts, opts = null;
}
try {
return this._render(view, opts, fn, parent, sub);
} catch (err) {
// callback given
if (fn) {
fn(err);
// unwind to root call to prevent multiple callbacks
} else if (sub) {
throw err;
// root template, next(err)
} else {
this.req.next(err);
}
}
};
// private render()
res._render = function(view, opts, fn, parent, sub){
var options = {}
, self = this
, app = this.app
, helpers = app._locals
, dynamicHelpers = app.dynamicViewHelpers
, viewOptions = app.set('view options')
, root = app.set('views') || process.cwd() + '/views';
// cache id
var cid = app.enabled('view cache')
? view + (parent ? ':' + parent.path : '')
: false;
// merge "view options"
if (viewOptions) merge(options, viewOptions);
// merge res._locals
if (this._locals) merge(options, this._locals);
// merge render() options
if (opts) merge(options, opts);
// merge render() .locals
if (opts && opts.locals) merge(options, opts.locals);
// status support
if (options.status) this.statusCode = options.status;
// capture attempts
options.attempts = [];
var partial = options.isPartial
, layout = options.layout;
// Layout support
if (true === layout || undefined === layout) {
layout = 'layout';
}
// Default execution scope to a plain object
options.scope = options.scope || {};
// Populate view
options.parentView = parent;
// "views" setting
options.root = root;
// "view engine" setting
options.defaultEngine = app.set('view engine');
// charset option
if (options.charset) this.charset = options.charset;
// Dynamic helper support
if (false !== options.dynamicHelpers) {
// cache
if (!this.__dynamicHelpers) {
this.__dynamicHelpers = {};
for (var key in dynamicHelpers) {
this.__dynamicHelpers[key] = dynamicHelpers[key].call(
this.app
, this.req
, this);
}
}
// apply
merge(options, this.__dynamicHelpers);
}
// Merge view helpers
union(options, helpers);
// Always expose partial() as a local
options.partial = function(path, opts){
return renderPartial(self, path, opts, options, view);
};
// View lookup
options.hint = app.enabled('hints');
view = exports.compile(view, app.cache, cid, options);
// layout helper
options.layout = function(path){
layout = path;
};
// render
var str = view.fn.call(options.scope, options);
// layout expected
if (layout) {
options.isLayout = true;
options.layout = false;
options.body = str;
this.render(layout, options, fn, view, true);
// partial return
} else if (partial) {
return str;
// render complete, and
// callback given
} else if (fn) {
fn(null, str);
// respond
} else {
this.send(str);
}
}
/**
* Hint at view path resolution, outputting the
* paths that Express has tried.
*
* @api private
*/
function hintAtViewPaths(view, options) {
console.error();
console.error('failed to locate view "' + view.view + '", tried:');
options.attempts.forEach(function(path){
console.error(' - %s', path);
});
console.error();
}

40
example/node/node_modules/express/lib/view/partial.js generated vendored Normal file
View file

@ -0,0 +1,40 @@
/*!
* Express - view - Partial
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Memory cache.
*/
var cache = {};
/**
* Resolve partial object name from the view path.
*
* Examples:
*
* "user.ejs" becomes "user"
* "forum thread.ejs" becomes "forumThread"
* "forum/thread/post.ejs" becomes "post"
* "blog-post.ejs" becomes "blogPost"
*
* @return {String}
* @api private
*/
exports.resolveObjectName = function(view){
return cache[view] || (cache[view] = view
.split('/')
.slice(-1)[0]
.split('.')[0]
.replace(/^_/, '')
.replace(/[^a-zA-Z0-9 ]+/g, ' ')
.split(/ +/).map(function(word, i){
return i
? word[0].toUpperCase() + word.substr(1)
: word;
}).join(''));
};

210
example/node/node_modules/express/lib/view/view.js generated vendored Normal file
View file

@ -0,0 +1,210 @@
/*!
* Express - View
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var path = require('path')
, utils = require('../utils')
, extname = path.extname
, dirname = path.dirname
, basename = path.basename
, fs = require('fs')
, stat = fs.statSync;
/**
* Expose `View`.
*/
exports = module.exports = View;
/**
* Require cache.
*/
var cache = {};
/**
* Initialize a new `View` with the given `view` path and `options`.
*
* @param {String} view
* @param {Object} options
* @api private
*/
function View(view, options) {
options = options || {};
this.view = view;
this.root = options.root;
this.relative = false !== options.relative;
this.defaultEngine = options.defaultEngine;
this.parent = options.parentView;
this.basename = basename(view);
this.engine = this.resolveEngine();
this.extension = '.' + this.engine;
this.name = this.basename.replace(this.extension, '');
this.path = this.resolvePath();
this.dirname = dirname(this.path);
if (options.attempts) {
if (!~options.attempts.indexOf(this.path))
options.attempts.push(this.path);
}
};
/**
* Check if the view path exists.
*
* @return {Boolean}
* @api public
*/
View.prototype.__defineGetter__('exists', function(){
try {
stat(this.path);
return true;
} catch (err) {
return false;
}
});
/**
* Resolve view engine.
*
* @return {String}
* @api private
*/
View.prototype.resolveEngine = function(){
// Explicit
if (~this.basename.indexOf('.')) return extname(this.basename).substr(1);
// Inherit from parent
if (this.parent) return this.parent.engine;
// Default
return this.defaultEngine;
};
/**
* Resolve view path.
*
* @return {String}
* @api private
*/
View.prototype.resolvePath = function(){
var path = this.view;
// Implicit engine
if (!~this.basename.indexOf('.')) path += this.extension;
// Absolute
if (utils.isAbsolute(path)) return path;
// Relative to parent
if (this.relative && this.parent) return this.parent.dirname + '/' + path;
// Relative to root
return this.root
? this.root + '/' + path
: path;
};
/**
* Get view contents. This is a one-time hit, so we
* can afford to be sync.
*
* @return {String}
* @api public
*/
View.prototype.__defineGetter__('contents', function(){
return fs.readFileSync(this.path, 'utf8');
});
/**
* Get template engine api, cache exports to reduce
* require() calls.
*
* @return {Object}
* @api public
*/
View.prototype.__defineGetter__('templateEngine', function(){
var ext = this.extension;
return cache[ext] || (cache[ext] = require(this.engine));
});
/**
* Return root path alternative.
*
* @return {String}
* @api public
*/
View.prototype.__defineGetter__('rootPath', function(){
this.relative = false;
return this.resolvePath();
});
/**
* Return index path alternative.
*
* @return {String}
* @api public
*/
View.prototype.__defineGetter__('indexPath', function(){
return this.dirname
+ '/' + this.basename.replace(this.extension, '')
+ '/index' + this.extension;
});
/**
* Return ../<name>/index path alternative.
*
* @return {String}
* @api public
*/
View.prototype.__defineGetter__('upIndexPath', function(){
return this.dirname + '/../' + this.name + '/index' + this.extension;
});
/**
* Return _ prefix path alternative
*
* @return {String}
* @api public
*/
View.prototype.__defineGetter__('prefixPath', function(){
return this.dirname + '/_' + this.basename;
});
/**
* Register the given template engine `exports`
* as `ext`. For example we may wish to map ".html"
* files to jade:
*
* app.register('.html', require('jade'));
*
* or
*
* app.register('html', require('jade'));
*
* This is also useful for libraries that may not
* match extensions correctly. For example my haml.js
* library is installed from npm as "hamljs" so instead
* of layout.hamljs, we can register the engine as ".haml":
*
* app.register('.haml', require('haml-js'));
*
* @param {String} ext
* @param {Object} obj
* @api public
*/
exports.register = function(ext, exports) {
if ('.' != ext[0]) ext = '.' + ext;
cache[ext] = exports;
};

View file

@ -0,0 +1,11 @@
*.markdown
*.md
.git*
Makefile
benchmarks/
docs/
examples/
install.sh
support/
test/
.DS_Store

View file

@ -0,0 +1,24 @@
(The MIT License)
Copyright (c) 2010 Sencha Inc.
Copyright (c) 2011 LearnBoost
Copyright (c) 2011 TJ Holowaychuk
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.

View file

@ -0,0 +1,2 @@
module.exports = require('./lib/connect');

View file

@ -0,0 +1,81 @@
/*!
* Connect - Cache
* Copyright(c) 2011 Sencha Inc.
* MIT Licensed
*/
/**
* Expose `Cache`.
*/
module.exports = Cache;
/**
* LRU cache store.
*
* @param {Number} limit
* @api private
*/
function Cache(limit) {
this.store = {};
this.keys = [];
this.limit = limit;
}
/**
* Touch `key`, promoting the object.
*
* @param {String} key
* @param {Number} i
* @api private
*/
Cache.prototype.touch = function(key, i){
this.keys.splice(i,1);
this.keys.push(key);
};
/**
* Remove `key`.
*
* @param {String} key
* @api private
*/
Cache.prototype.remove = function(key){
delete this.store[key];
};
/**
* Get the object stored for `key`.
*
* @param {String} key
* @return {Array}
* @api private
*/
Cache.prototype.get = function(key){
return this.store[key];
};
/**
* Add a cache `key`.
*
* @param {String} key
* @return {Array}
* @api private
*/
Cache.prototype.add = function(key){
// initialize store
var len = this.keys.push(key);
// limit reached, invalid LRU
if (len > this.limit) this.remove(this.keys.shift());
var arr = this.store[key] = [];
arr.createdAt = new Date;
return arr;
};

View file

@ -0,0 +1,106 @@
/*!
* Connect
* Copyright(c) 2010 Sencha Inc.
* Copyright(c) 2011 TJ Holowaychuk
* MIT Licensed
*/
/**
* Module dependencies.
*/
var HTTPServer = require('./http').Server
, HTTPSServer = require('./https').Server
, fs = require('fs');
// node patches
require('./patch');
// expose createServer() as the module
exports = module.exports = createServer;
/**
* Framework version.
*/
exports.version = '1.8.6';
/**
* Initialize a new `connect.HTTPServer` with the middleware
* passed to this function. When an object is passed _first_,
* we assume these are the tls options, and return a `connect.HTTPSServer`.
*
* Examples:
*
* An example HTTP server, accepting several middleware.
*
* var server = connect.createServer(
* connect.logger()
* , connect.static(__dirname + '/public')
* );
*
* An HTTPS server, utilizing the same middleware as above.
*
* var server = connect.createServer(
* { key: key, cert: cert }
* , connect.logger()
* , connect.static(__dirname + '/public')
* );
*
* Alternatively with connect 1.0 we may omit `createServer()`.
*
* connect(
* connect.logger()
* , connect.static(__dirname + '/public')
* ).listen(3000);
*
* @param {Object|Function} ...
* @return {Server}
* @api public
*/
function createServer() {
if ('object' == typeof arguments[0]) {
return new HTTPSServer(arguments[0], Array.prototype.slice.call(arguments, 1));
} else {
return new HTTPServer(Array.prototype.slice.call(arguments));
}
};
// support connect.createServer()
exports.createServer = createServer;
// auto-load getters
exports.middleware = {};
/**
* Auto-load bundled middleware with getters.
*/
fs.readdirSync(__dirname + '/middleware').forEach(function(filename){
if (/\.js$/.test(filename)) {
var name = filename.substr(0, filename.lastIndexOf('.'));
exports.middleware.__defineGetter__(name, function(){
return require('./middleware/' + name);
});
}
});
// expose utils
exports.utils = require('./utils');
// expose getters as first-class exports
exports.utils.merge(exports, exports.middleware);
// expose constructors
exports.HTTPServer = HTTPServer;
exports.HTTPSServer = HTTPSServer;

View file

@ -0,0 +1,217 @@
/*!
* Connect - HTTPServer
* Copyright(c) 2010 Sencha Inc.
* Copyright(c) 2011 TJ Holowaychuk
* MIT Licensed
*/
/**
* Module dependencies.
*/
var http = require('http')
, parse = require('url').parse
, assert = require('assert');
// environment
var env = process.env.NODE_ENV || 'development';
/**
* Initialize a new `Server` with the given `middleware`.
*
* Examples:
*
* var server = connect.createServer(
* connect.favicon()
* , connect.logger()
* , connect.static(__dirname + '/public')
* );
*
* @params {Array} middleware
* @return {Server}
* @api public
*/
var Server = exports.Server = function HTTPServer(middleware) {
this.stack = [];
middleware.forEach(function(fn){
this.use(fn);
}, this);
http.Server.call(this, this.handle);
};
/**
* Inherit from `http.Server.prototype`.
*/
Server.prototype.__proto__ = http.Server.prototype;
/**
* Utilize the given middleware `handle` to the given `route`,
* defaulting to _/_. This "route" is the mount-point for the
* middleware, when given a value other than _/_ the middleware
* is only effective when that segment is present in the request's
* pathname.
*
* For example if we were to mount a function at _/admin_, it would
* be invoked on _/admin_, and _/admin/settings_, however it would
* not be invoked for _/_, or _/posts_.
*
* This is effectively the same as passing middleware to `connect.createServer()`,
* however provides a progressive api.
*
* Examples:
*
* var server = connect.createServer();
* server.use(connect.favicon());
* server.use(connect.logger());
* server.use(connect.static(__dirname + '/public'));
*
* If we wanted to prefix static files with _/public_, we could
* "mount" the `static()` middleware:
*
* server.use('/public', connect.static(__dirname + '/public'));
*
* This api is chainable, meaning the following is valid:
*
* connect.createServer()
* .use(connect.favicon())
* .use(connect.logger())
* .use(connect.static(__dirname + '/public'))
* .listen(3000);
*
* @param {String|Function} route or handle
* @param {Function} handle
* @return {Server}
* @api public
*/
Server.prototype.use = function(route, handle){
this.route = '/';
// default route to '/'
if ('string' != typeof route) {
handle = route;
route = '/';
}
// wrap sub-apps
if ('function' == typeof handle.handle) {
var server = handle;
server.route = route;
handle = function(req, res, next) {
server.handle(req, res, next);
};
}
// wrap vanilla http.Servers
if (handle instanceof http.Server) {
handle = handle.listeners('request')[0];
}
// normalize route to not trail with slash
if ('/' == route[route.length - 1]) {
route = route.substr(0, route.length - 1);
}
// add the middleware
this.stack.push({ route: route, handle: handle });
// allow chaining
return this;
};
/**
* Handle server requests, punting them down
* the middleware stack.
*
* @api private
*/
Server.prototype.handle = function(req, res, out) {
var writeHead = res.writeHead
, stack = this.stack
, removed = ''
, index = 0;
function next(err) {
var layer, path, c;
req.url = removed + req.url;
req.originalUrl = req.originalUrl || req.url;
removed = '';
layer = stack[index++];
// all done
if (!layer || res.headerSent) {
// but wait! we have a parent
if (out) return out(err);
// error
if (err) {
var msg = 'production' == env
? 'Internal Server Error'
: err.stack || err.toString();
// output to stderr in a non-test env
if ('test' != env) console.error(err.stack || err.toString());
// unable to respond
if (res.headerSent) return req.socket.destroy();
res.statusCode = 500;
res.setHeader('Content-Type', 'text/plain');
if ('HEAD' == req.method) return res.end();
res.end(msg);
} else {
res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain');
if ('HEAD' == req.method) return res.end();
res.end('Cannot ' + req.method + ' ' + req.url);
}
return;
}
try {
path = parse(req.url).pathname;
if (undefined == path) path = '/';
// skip this layer if the route doesn't match.
if (0 != path.indexOf(layer.route)) return next(err);
c = path[layer.route.length];
if (c && '/' != c && '.' != c) return next(err);
// Call the layer handler
// Trim off the part of the url that matches the route
removed = layer.route;
req.url = req.url.substr(removed.length);
// Ensure leading slash
if ('/' != req.url[0]) req.url = '/' + req.url;
var arity = layer.handle.length;
if (err) {
if (arity === 4) {
layer.handle(err, req, res, next);
} else {
next(err);
}
} else if (arity < 4) {
layer.handle(req, res, next);
} else {
next();
}
} catch (e) {
if (e instanceof assert.AssertionError) {
console.error(e.stack + '\n');
next(e);
} else {
next(e);
}
}
}
next();
};

View file

@ -0,0 +1,47 @@
/*!
* Connect - HTTPServer
* Copyright(c) 2010 Sencha Inc.
* Copyright(c) 2011 TJ Holowaychuk
* MIT Licensed
*/
/**
* Module dependencies.
*/
var HTTPServer = require('./http').Server
, https = require('https');
/**
* Initialize a new `Server` with the given
*`options` and `middleware`. The HTTPS api
* is identical to the [HTTP](http.html) server,
* however TLS `options` must be provided before
* passing in the optional middleware.
*
* @params {Object} options
* @params {Array} middleawre
* @return {Server}
* @api public
*/
var Server = exports.Server = function HTTPSServer(options, middleware) {
this.stack = [];
middleware.forEach(function(fn){
this.use(fn);
}, this);
https.Server.call(this, options, this.handle);
};
/**
* Inherit from `http.Server.prototype`.
*/
Server.prototype.__proto__ = https.Server.prototype;
// mixin HTTPServer methods
Object.keys(HTTPServer.prototype).forEach(function(method){
Server.prototype[method] = HTTPServer.prototype[method];
});

View file

@ -0,0 +1,46 @@
/**
* # Connect
*
* Connect is a middleware framework for node,
* shipping with over 11 bundled middleware and a rich choice of
* [3rd-party middleware](https://github.com/senchalabs/connect/wiki).
*
* Installation:
*
* $ npm install connect
*
* API:
*
* - [connect](connect.html) general
* - [http](http.html) http server
* - [https](https.html) https server
*
* Middleware:
*
* - [logger](middleware-logger.html) request logger with custom format support
* - [csrf](middleware-csrf.html) Cross-site request forgery protection
* - [basicAuth](middleware-basicAuth.html) basic http authentication
* - [bodyParser](middleware-bodyParser.html) extensible request body parser
* - [cookieParser](middleware-cookieParser.html) cookie parser
* - [session](middleware-session.html) session management support with bundled [MemoryStore](middleware-session-memory.html)
* - [compiler](middleware-compiler.html) static asset compiler (sass, less, coffee-script, etc)
* - [methodOverride](middleware-methodOverride.html) faux HTTP method support
* - [responseTime](middleware-responseTime.html) calculates response-time and exposes via X-Response-Time
* - [router](middleware-router.html) provides rich Sinatra / Express-like routing
* - [staticCache](middleware-staticCache.html) memory cache layer for the static() middleware
* - [static](middleware-static.html) streaming static file server supporting `Range` and more
* - [directory](middleware-directory.html) directory listing middleware
* - [vhost](middleware-vhost.html) virtual host sub-domain mapping middleware
* - [favicon](middleware-favicon.html) efficient favicon server (with default icon)
* - [limit](middleware-limit.html) limit the bytesize of request bodies
* - [profiler](middleware-profiler.html) request profiler reporting response-time, memory usage, etc
* - [query](middleware-query.html) automatic querystring parser, populating `req.query`
* - [errorHandler](middleware-errorHandler.html) flexible error handler
*
* Internals:
*
* - connect [utilities](utils.html)
* - node monkey [patches](patch.html)
*
*/

View file

@ -0,0 +1,93 @@
/*!
* Connect - basicAuth
* Copyright(c) 2010 Sencha Inc.
* Copyright(c) 2011 TJ Holowaychuk
* MIT Licensed
*/
/**
* Module dependencies.
*/
var utils = require('../utils')
, unauthorized = utils.unauthorized
, badRequest = utils.badRequest;
/**
* Enfore basic authentication by providing a `callback(user, pass)`,
* which must return `true` in order to gain access. Alternatively an async
* method is provided as well, invoking `callback(user, pass, callback)`. Populates
* `req.remoteUser`. The final alternative is simply passing username / password
* strings.
*
* Examples:
*
* connect(connect.basicAuth('username', 'password'));
*
* connect(
* connect.basicAuth(function(user, pass){
* return 'tj' == user & 'wahoo' == pass;
* })
* );
*
* connect(
* connect.basicAuth(function(user, pass, fn){
* User.authenticate({ user: user, pass: pass }, fn);
* })
* );
*
* @param {Function|String} callback or username
* @param {String} realm
* @api public
*/
module.exports = function basicAuth(callback, realm) {
var username, password;
// user / pass strings
if ('string' == typeof callback) {
username = callback;
password = realm;
if ('string' != typeof password) throw new Error('password argument required');
realm = arguments[2];
callback = function(user, pass){
return user == username && pass == password;
}
}
realm = realm || 'Authorization Required';
return function(req, res, next) {
var authorization = req.headers.authorization;
if (req.remoteUser) return next();
if (!authorization) return unauthorized(res, realm);
var parts = authorization.split(' ')
, scheme = parts[0]
, credentials = new Buffer(parts[1], 'base64').toString().split(':');
if ('Basic' != scheme) return badRequest(res);
// async
if (callback.length >= 3) {
var pause = utils.pause(req);
callback(credentials[0], credentials[1], function(err, user){
if (err || !user) return unauthorized(res, realm);
req.remoteUser = user;
next();
pause.resume();
});
// sync
} else {
if (callback(credentials[0], credentials[1])) {
req.remoteUser = credentials[0];
next();
} else {
unauthorized(res, realm);
}
}
}
};

View file

@ -0,0 +1,196 @@
/*!
* Connect - bodyParser
* Copyright(c) 2010 Sencha Inc.
* Copyright(c) 2011 TJ Holowaychuk
* MIT Licensed
*/
/**
* Module dependencies.
*/
var qs = require('qs')
, formidable = require('formidable');
/**
* Extract the mime type from the given request's
* _Content-Type_ header.
*
* @param {IncomingMessage} req
* @return {String}
* @api private
*/
function mime(req) {
var str = req.headers['content-type'] || '';
return str.split(';')[0];
}
/**
* Parse request bodies.
*
* By default _application/json_, _application/x-www-form-urlencoded_,
* and _multipart/form-data_ are supported, however you may map `connect.bodyParser.parse[contentType]`
* to a function receiving `(req, options, callback)`.
*
* Examples:
*
* connect.createServer(
* connect.bodyParser()
* , function(req, res) {
* res.end('viewing user ' + req.body.user.name);
* }
* );
*
* $ curl -d 'user[name]=tj' http://localhost/
* $ curl -d '{"user":{"name":"tj"}}' -H "Content-Type: application/json" http://localhost/
*
* Multipart req.files:
*
* As a security measure files are stored in a separate object, stored
* as `req.files`. This prevents attacks that may potentially alter
* filenames, and depending on the application gain access to restricted files.
*
* Multipart configuration:
*
* The `options` passed are provided to each parser function.
* The _multipart/form-data_ parser merges these with formidable's
* IncomingForm object, allowing you to tweak the upload directory,
* size limits, etc. For example you may wish to retain the file extension
* and change the upload directory:
*
* server.use(bodyParser({ uploadDir: '/www/mysite.com/uploads' }));
*
* View [node-formidable](https://github.com/felixge/node-formidable) for more information.
*
* If you wish to use formidable directly within your app, and do not
* desire this behaviour for multipart requests simply remove the
* parser:
*
* delete connect.bodyParser.parse['multipart/form-data'];
*
* Or
*
* delete express.bodyParser.parse['multipart/form-data'];
*
* @param {Object} options
* @return {Function}
* @api public
*/
exports = module.exports = function bodyParser(options){
options = options || {};
return function bodyParser(req, res, next) {
if (req.body) return next();
req.body = {};
if ('GET' == req.method || 'HEAD' == req.method) return next();
var parser = exports.parse[mime(req)];
if (parser) {
parser(req, options, next);
} else {
next();
}
}
};
/**
* Parsers.
*/
exports.parse = {};
/**
* Parse application/x-www-form-urlencoded.
*/
exports.parse['application/x-www-form-urlencoded'] = function(req, options, fn){
var buf = '';
req.setEncoding('utf8');
req.on('data', function(chunk){ buf += chunk });
req.on('end', function(){
try {
req.body = buf.length
? qs.parse(buf)
: {};
fn();
} catch (err){
fn(err);
}
});
};
/**
* Parse application/json.
*/
exports.parse['application/json'] = function(req, options, fn){
var buf = '';
req.setEncoding('utf8');
req.on('data', function(chunk){ buf += chunk });
req.on('end', function(){
try {
req.body = buf.length
? JSON.parse(buf)
: {};
fn();
} catch (err){
fn(err);
}
});
};
/**
* Parse multipart/form-data.
*
* TODO: make multiple support optional
* TODO: revisit "error" flag if it's a formidable bug
*/
exports.parse['multipart/form-data'] = function(req, options, fn){
var form = new formidable.IncomingForm
, data = {}
, files = {}
, done;
Object.keys(options).forEach(function(key){
form[key] = options[key];
});
function ondata(name, val, data){
if (Array.isArray(data[name])) {
data[name].push(val);
} else if (data[name]) {
data[name] = [data[name], val];
} else {
data[name] = val;
}
}
form.on('field', function(name, val){
ondata(name, val, data);
});
form.on('file', function(name, val){
ondata(name, val, files);
});
form.on('error', function(err){
fn(err);
done = true;
});
form.on('end', function(){
if (done) return;
try {
req.body = qs.parse(data);
req.files = qs.parse(files);
fn();
} catch (err) {
fn(err);
}
});
form.parse(req);
};

View file

@ -0,0 +1,163 @@
/*!
* Connect - compiler
* Copyright(c) 2010 Sencha Inc.
* Copyright(c) 2011 TJ Holowaychuk
* MIT Licensed
*/
/**
* Module dependencies.
*/
var fs = require('fs')
, path = require('path')
, parse = require('url').parse;
/**
* Require cache.
*/
var cache = {};
/**
* Setup compiler.
*
* Options:
*
* - `src` Source directory, defaults to **CWD**.
* - `dest` Destination directory, defaults `src`.
* - `enable` Array of enabled compilers.
*
* Compilers:
*
* - `sass` Compiles sass to css
* - `less` Compiles less to css
* - `coffeescript` Compiles coffee to js
*
* @param {Object} options
* @api public
*/
exports = module.exports = function compiler(options){
options = options || {};
var srcDir = options.src || process.cwd()
, destDir = options.dest || srcDir
, enable = options.enable;
if (!enable || enable.length === 0) {
throw new Error('compiler\'s "enable" option is not set, nothing will be compiled.');
}
return function compiler(req, res, next){
if ('GET' != req.method) return next();
var pathname = parse(req.url).pathname;
for (var i = 0, len = enable.length; i < len; ++i) {
var name = enable[i]
, compiler = compilers[name];
if (compiler.match.test(pathname)) {
var src = (srcDir + pathname).replace(compiler.match, compiler.ext)
, dest = destDir + pathname;
// Compare mtimes
fs.stat(src, function(err, srcStats){
if (err) {
if ('ENOENT' == err.code) {
next();
} else {
next(err);
}
} else {
fs.stat(dest, function(err, destStats){
if (err) {
// Oh snap! it does not exist, compile it
if ('ENOENT' == err.code) {
compile();
} else {
next(err);
}
} else {
// Source has changed, compile it
if (srcStats.mtime > destStats.mtime) {
compile();
} else {
// Defer file serving
next();
}
}
});
}
});
// Compile to the destination
function compile() {
fs.readFile(src, 'utf8', function(err, str){
if (err) {
next(err);
} else {
compiler.compile(str, function(err, str){
if (err) {
next(err);
} else {
fs.writeFile(dest, str, 'utf8', function(err){
next(err);
});
}
});
}
});
}
return;
}
}
next();
};
};
/**
* Bundled compilers:
*
* - [sass](http://github.com/visionmedia/sass.js) to _css_
* - [less](http://github.com/cloudhead/less.js) to _css_
* - [coffee](http://github.com/jashkenas/coffee-script) to _js_
*/
var compilers = exports.compilers = {
sass: {
match: /\.css$/,
ext: '.sass',
compile: function(str, fn){
var sass = cache.sass || (cache.sass = require('sass'));
try {
fn(null, sass.render(str));
} catch (err) {
fn(err);
}
}
},
less: {
match: /\.css$/,
ext: '.less',
compile: function(str, fn){
var less = cache.less || (cache.less = require('less'));
try {
less.render(str, fn);
} catch (err) {
fn(err);
}
}
},
coffeescript: {
match: /\.js$/,
ext: '.coffee',
compile: function(str, fn){
var coffee = cache.coffee || (cache.coffee = require('coffee-script'));
try {
fn(null, coffee.compile(str));
} catch (err) {
fn(err);
}
}
}
};

View file

@ -0,0 +1,46 @@
/*!
* Connect - cookieParser
* Copyright(c) 2010 Sencha Inc.
* Copyright(c) 2011 TJ Holowaychuk
* MIT Licensed
*/
/**
* Module dependencies.
*/
var utils = require('./../utils');
/**
* Parse _Cookie_ header and populate `req.cookies`
* with an object keyed by the cookie names.
*
* Examples:
*
* connect.createServer(
* connect.cookieParser()
* , function(req, res, next){
* res.end(JSON.stringify(req.cookies));
* }
* );
*
* @return {Function}
* @api public
*/
module.exports = function cookieParser(){
return function cookieParser(req, res, next) {
var cookie = req.headers.cookie;
if (req.cookies) return next();
req.cookies = {};
if (cookie) {
try {
req.cookies = utils.parseCookie(cookie);
} catch (err) {
return next(err);
}
}
next();
};
};

View file

@ -0,0 +1,105 @@
/*!
* Connect - csrf
* Copyright(c) 2011 Sencha Inc.
* MIT Licensed
*/
/**
* Module dependencies.
*/
var utils = require('../utils')
, crypto = require('crypto');
/**
* CRSF protection middleware.
*
* By default this middleware generates a token named "_csrf"
* which should be added to requests which mutate
* state, within a hidden form field, query-string etc. This
* token is validated against the visitor's `req.session._csrf`
* property which is re-generated per request.
*
* The default `value` function checks `req.body` generated
* by the `bodyParser()` middleware, `req.query` generated
* by `query()`, and the "X-CSRF-Token" header field.
*
* This middleware requires session support, thus should be added
* somewhere _below_ `session()` and `cookieParser()`.
*
* Examples:
*
* var form = '\n\
* <form action="/" method="post">\n\
* <input type="hidden" name="_csrf" value="{token}" />\n\
* <input type="text" name="user[name]" value="{user}" />\n\
* <input type="password" name="user[pass]" />\n\
* <input type="submit" value="Login" />\n\
* </form>\n\
* ';
*
* connect(
* connect.cookieParser()
* , connect.session({ secret: 'keyboard cat' })
* , connect.bodyParser()
* , connect.csrf()
*
* , function(req, res, next){
* if ('POST' != req.method) return next();
* req.session.user = req.body.user;
* next();
* }
*
* , function(req, res){
* res.setHeader('Content-Type', 'text/html');
* var body = form
* .replace('{token}', req.session._csrf)
* .replace('{user}', req.session.user && req.session.user.name || '');
* res.end(body);
* }
* ).listen(3000);
*
* Options:
*
* - `value` a function accepting the request, returning the token
*
* @param {Object} options
* @api public
*/
module.exports = function csrf(options) {
var options = options || {}
, value = options.value || defaultValue;
return function(req, res, next){
// generate CSRF token
var token = req.session._csrf || (req.session._csrf = utils.uid(24));
// ignore GET (for now)
if ('GET' == req.method) return next();
// determine value
var val = value(req);
// check
if (val != token) return utils.forbidden(res);
next();
}
};
/**
* Default value function, checking the `req.body`
* and `req.query` for the CSRF token.
*
* @param {IncomingMessage} req
* @return {String}
* @api private
*/
function defaultValue(req) {
return (req.body && req.body._csrf)
|| (req.query && req.query._csrf)
|| (req.headers['x-csrf-token']);
}

View file

@ -0,0 +1,222 @@
/*!
* Connect - directory
* Copyright(c) 2011 Sencha Inc.
* Copyright(c) 2011 TJ Holowaychuk
* MIT Licensed
*/
// TODO: icon / style for directories
// TODO: arrow key navigation
// TODO: make icons extensible
/**
* Module dependencies.
*/
var fs = require('fs')
, parse = require('url').parse
, utils = require('../utils')
, path = require('path')
, normalize = path.normalize
, extname = path.extname
, join = path.join;
/**
* Icon cache.
*/
var cache = {};
/**
* Serve directory listings with the given `root` path.
*
* Options:
*
* - `hidden` display hidden (dot) files. Defaults to false.
* - `icons` display icons. Defaults to false.
* - `filter` Apply this filter function to files. Defaults to false.
*
* @param {String} root
* @param {Object} options
* @return {Function}
* @api public
*/
exports = module.exports = function directory(root, options){
options = options || {};
// root required
if (!root) throw new Error('directory() root path required');
var hidden = options.hidden
, icons = options.icons
, filter = options.filter
, root = normalize(root);
return function directory(req, res, next) {
var accept = req.headers.accept || 'text/plain'
, url = parse(req.url)
, dir = decodeURIComponent(url.pathname)
, path = normalize(join(root, dir))
, originalUrl = parse(req.originalUrl)
, originalDir = decodeURIComponent(originalUrl.pathname)
, showUp = path != root && path != root + '/';
// null byte(s)
if (~path.indexOf('\0')) return utils.badRequest(res);
// malicious path
if (0 != path.indexOf(root)) return utils.forbidden(res);
// check if we have a directory
fs.stat(path, function(err, stat){
if (err) return 'ENOENT' == err.code
? next()
: next(err);
if (!stat.isDirectory()) return next();
// fetch files
fs.readdir(path, function(err, files){
if (err) return next(err);
if (!hidden) files = removeHidden(files);
if (filter) files = files.filter(filter);
files.sort();
// content-negotiation
for (var key in exports) {
if (~accept.indexOf(key) || ~accept.indexOf('*/*')) {
exports[key](req, res, files, next, originalDir, showUp, icons);
return;
}
}
utils.notAcceptable(res);
});
});
};
};
/**
* Respond with text/html.
*/
exports.html = function(req, res, files, next, dir, showUp, icons){
fs.readFile(__dirname + '/../public/directory.html', 'utf8', function(err, str){
if (err) return next(err);
fs.readFile(__dirname + '/../public/style.css', 'utf8', function(err, style){
if (err) return next(err);
if (showUp) files.unshift('..');
str = str
.replace('{style}', style)
.replace('{files}', html(files, dir, icons))
.replace('{directory}', dir)
.replace('{linked-path}', htmlPath(dir));
res.setHeader('Content-Type', 'text/html');
res.setHeader('Content-Length', str.length);
res.end(str);
});
});
};
/**
* Respond with application/json.
*/
exports.json = function(req, res, files){
files = JSON.stringify(files);
res.setHeader('Content-Type', 'application/json');
res.setHeader('Content-Length', files.length);
res.end(files);
};
/**
* Respond with text/plain.
*/
exports.plain = function(req, res, files){
files = files.join('\n') + '\n';
res.setHeader('Content-Type', 'text/plain');
res.setHeader('Content-Length', files.length);
res.end(files);
};
/**
* Map html `dir`, returning a linked path.
*/
function htmlPath(dir) {
var curr = [];
return dir.split('/').map(function(part){
curr.push(part);
return '<a href="' + curr.join('/') + '">' + part + '</a>';
}).join(' / ');
}
/**
* Map html `files`, returning an html unordered list.
*/
function html(files, dir, useIcons) {
return '<ul id="files">' + files.map(function(file){
var icon = ''
, classes = [];
if (useIcons && '..' != file) {
icon = icons[extname(file)] || icons.default;
icon = '<img src="data:image/png;base64,' + load(icon) + '" />';
classes.push('icon');
}
return '<li><a href="'
+ join(dir, file)
+ '" class="'
+ classes.join(' ') + '"'
+ ' title="' + file + '">'
+ icon + file + '</a></li>';
}).join('\n') + '</ul>';
}
/**
* Load and cache the given `icon`.
*
* @param {String} icon
* @return {String}
* @api private
*/
function load(icon) {
if (cache[icon]) return cache[icon];
return cache[icon] = fs.readFileSync(__dirname + '/../public/icons/' + icon, 'base64');
}
/**
* Filter "hidden" `files`, aka files
* beginning with a `.`.
*
* @param {Array} files
* @return {Array}
* @api private
*/
function removeHidden(files) {
return files.filter(function(file){
return '.' != file[0];
});
}
/**
* Icon map.
*/
var icons = {
'.js': 'page_white_code_red.png'
, '.c': 'page_white_c.png'
, '.h': 'page_white_h.png'
, '.cc': 'page_white_cplusplus.png'
, '.php': 'page_white_php.png'
, '.rb': 'page_white_ruby.png'
, '.cpp': 'page_white_cplusplus.png'
, '.swf': 'page_white_flash.png'
, '.pdf': 'page_white_acrobat.png'
, 'default': 'page_white.png'
};

View file

@ -0,0 +1,100 @@
/*!
* Connect - errorHandler
* Copyright(c) 2010 Sencha Inc.
* Copyright(c) 2011 TJ Holowaychuk
* MIT Licensed
*/
/**
* Module dependencies.
*/
var utils = require('../utils')
, url = require('url')
, fs = require('fs');
/**
* Flexible error handler, providing (_optional_) stack traces
* and error message responses for requests accepting text, html,
* or json.
*
* Options:
*
* - `showStack`, `stack` respond with both the error message and stack trace. Defaults to `false`
* - `showMessage`, `message`, respond with the exception message only. Defaults to `false`
* - `dumpExceptions`, `dump`, dump exceptions to stderr (without terminating the process). Defaults to `false`
*
* Text:
*
* By default, and when _text/plain_ is accepted a simple stack trace
* or error message will be returned.
*
* JSON:
*
* When _application/json_ is accepted, connect will respond with
* an object in the form of `{ "error": error }`.
*
* HTML:
*
* When accepted connect will output a nice html stack trace.
*
* @param {Object} options
* @return {Function}
* @api public
*/
exports = module.exports = function errorHandler(options){
options = options || {};
// defaults
var showStack = options.showStack || options.stack
, showMessage = options.showMessage || options.message
, dumpExceptions = options.dumpExceptions || options.dump
, formatUrl = options.formatUrl;
return function errorHandler(err, req, res, next){
res.statusCode = 500;
if (dumpExceptions) console.error(err.stack);
if (showStack) {
var accept = req.headers.accept || '';
// html
if (~accept.indexOf('html')) {
fs.readFile(__dirname + '/../public/style.css', 'utf8', function(e, style){
fs.readFile(__dirname + '/../public/error.html', 'utf8', function(e, html){
var stack = (err.stack || '')
.split('\n').slice(1)
.map(function(v){ return '<li>' + v + '</li>'; }).join('');
html = html
.replace('{style}', style)
.replace('{stack}', stack)
.replace('{title}', exports.title)
.replace(/\{error\}/g, utils.escape(err.toString()));
res.setHeader('Content-Type', 'text/html');
res.end(html);
});
});
// json
} else if (~accept.indexOf('json')) {
var json = JSON.stringify({ error: err });
res.setHeader('Content-Type', 'application/json');
res.end(json);
// plain text
} else {
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end(err.stack);
}
} else {
var body = showMessage
? err.toString()
: 'Internal Server Error';
res.setHeader('Content-Type', 'text/plain');
res.end(body);
}
};
};
/**
* Template title.
*/
exports.title = 'Connect';

View file

@ -0,0 +1,76 @@
/*!
* Connect - favicon
* Copyright(c) 2010 Sencha Inc.
* Copyright(c) 2011 TJ Holowaychuk
* MIT Licensed
*/
/**
* Module dependencies.
*/
var fs = require('fs')
, utils = require('../utils');
/**
* Favicon cache.
*/
var icon;
/**
* By default serves the connect favicon, or the favicon
* located by the given `path`.
*
* Options:
*
* - `maxAge` cache-control max-age directive, defaulting to 1 day
*
* Examples:
*
* connect.createServer(
* connect.favicon()
* );
*
* connect.createServer(
* connect.favicon(__dirname + '/public/favicon.ico')
* );
*
* @param {String} path
* @param {Object} options
* @return {Function}
* @api public
*/
module.exports = function favicon(path, options){
var options = options || {}
, path = path || __dirname + '/../public/favicon.ico'
, maxAge = options.maxAge || 86400000;
return function favicon(req, res, next){
if ('/favicon.ico' == req.url) {
if (icon) {
res.writeHead(200, icon.headers);
res.end(icon.body);
} else {
fs.readFile(path, function(err, buf){
if (err) return next(err);
icon = {
headers: {
'Content-Type': 'image/x-icon'
, 'Content-Length': buf.length
, 'ETag': '"' + utils.md5(buf) + '"'
, 'Cache-Control': 'public, max-age=' + (maxAge / 1000)
},
body: buf
};
res.writeHead(200, icon.headers);
res.end(icon.body);
});
}
} else {
next();
}
};
};

View file

@ -0,0 +1,80 @@
/*!
* Connect - limit
* Copyright(c) 2011 TJ Holowaychuk
* MIT Licensed
*/
/**
* Limit request bodies to the given size in `bytes`.
*
* A string representation of the bytesize may also be passed,
* for example "5mb", "200kb", "1gb", etc.
*
* Examples:
*
* var server = connect(
* connect.limit('5.5mb')
* ).listen(3000);
*
* @param {Number|String} bytes
* @return {Function}
* @api public
*/
module.exports = function limit(bytes){
if ('string' == typeof bytes) bytes = parse(bytes);
if ('number' != typeof bytes) throw new Error('limit() bytes required');
return function limit(req, res, next){
var received = 0
, len = req.headers['content-length']
? parseInt(req.headers['content-length'], 10)
: null;
// deny the request
function deny() {
req.destroy();
}
// self-awareness
if (req._limit) return next();
req._limit = true;
// limit by content-length
if (len && len > bytes) {
res.statusCode = 413;
res.end('Request Entity Too Large');
return;
}
// limit
req.on('data', function(chunk){
received += chunk.length;
if (received > bytes) deny();
});
next();
};
};
/**
* Parse byte `size` string.
*
* @param {String} size
* @return {Number}
* @api private
*/
function parse(size) {
var parts = size.match(/^(\d+(?:\.\d+)?) *(kb|mb|gb)$/)
, n = parseFloat(parts[1])
, type = parts[2];
var map = {
kb: 1024
, mb: 1024 * 1024
, gb: 1024 * 1024 * 1024
};
return map[type] * n;
}

View file

@ -0,0 +1,299 @@
/*!
* Connect - logger
* Copyright(c) 2010 Sencha Inc.
* Copyright(c) 2011 TJ Holowaychuk
* MIT Licensed
*/
/**
* Log buffer.
*/
var buf = [];
/**
* Default log buffer duration.
*/
var defaultBufferDuration = 1000;
/**
* Log requests with the given `options` or a `format` string.
*
* Options:
*
* - `format` Format string, see below for tokens
* - `stream` Output stream, defaults to _stdout_
* - `buffer` Buffer duration, defaults to 1000ms when _true_
* - `immediate` Write log line on request instead of response (for response times)
*
* Tokens:
*
* - `:req[header]` ex: `:req[Accept]`
* - `:res[header]` ex: `:res[Content-Length]`
* - `:http-version`
* - `:response-time`
* - `:remote-addr`
* - `:date`
* - `:method`
* - `:url`
* - `:referrer`
* - `:user-agent`
* - `:status`
*
* Formats:
*
* Pre-defined formats that ship with connect:
*
* - `default` ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"'
* - `short` ':remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms'
* - `tiny` ':method :url :status :res[content-length] - :response-time ms'
* - `dev` concise output colored by response status for development use
*
* Examples:
*
* connect.logger() // default
* connect.logger('short')
* connect.logger('tiny')
* connect.logger('dev')
* connect.logger(':method :url - :referrer')
* connect.logger(':req[content-type] -> :res[content-type]')
* connect.logger(function(req, res){ return 'some format string' })
*
* Defining Tokens:
*
* To define a token, simply invoke `connect.logger.token()` with the
* name and a callback function. The value returned is then available
* as ":type" in this case.
*
* connect.logger.token('type', function(req, res){ return req.headers['content-type']; })
*
* Defining Formats:
*
* All default formats are defined this way, however it's public API as well:
*
* connect.logger.format('name', 'string or function')
*
* @param {String|Function|Object} format or options
* @return {Function}
* @api public
*/
exports = module.exports = function logger(options) {
if ('object' == typeof options) {
options = options || {};
} else if (options) {
options = { format: options };
} else {
options = {};
}
// output on request instead of response
var immediate = options.immediate;
// format name
var fmt = exports[options.format] || options.format || exports.default;
// compile format
if ('function' != typeof fmt) fmt = compile(fmt);
// options
var stream = options.stream || process.stdout
, buffer = options.buffer;
// buffering support
if (buffer) {
var realStream = stream
, interval = 'number' == typeof buffer
? buffer
: defaultBufferDuration;
// flush interval
setInterval(function(){
if (buf.length) {
realStream.write(buf.join(''), 'ascii');
buf.length = 0;
}
}, interval);
// swap the stream
stream = {
write: function(str){
buf.push(str);
}
};
}
return function logger(req, res, next) {
req._startTime = new Date;
// mount safety
if (req._logging) return next();
// flag as logging
req._logging = true;
// immediate
if (immediate) {
var line = fmt(exports, req, res);
if (null == line) return;
stream.write(line + '\n', 'ascii');
} else {
// proxy end to output loggging
var end = res.end;
res.end = function(chunk, encoding){
res.end = end;
res.end(chunk, encoding);
var line = fmt(exports, req, res);
if (null == line) return;
stream.write(line + '\n', 'ascii');
};
}
next();
};
};
/**
* Compile `fmt` into a function.
*
* @param {String} fmt
* @return {Function}
* @api private
*/
function compile(fmt) {
fmt = fmt.replace(/"/g, '\\"');
var js = ' return "' + fmt.replace(/:([-\w]{2,})(?:\[([^\]]+)\])?/g, function(_, name, arg){
return '"\n + (tokens["' + name + '"](req, res, "' + arg + '") || "-") + "';
}) + '";'
return new Function('tokens, req, res', js);
};
/**
* Define a token function with the given `name`,
* and callback `fn(req, res)`.
*
* @param {String} name
* @param {Function} fn
* @return {Object} exports for chaining
* @api public
*/
exports.token = function(name, fn) {
exports[name] = fn;
return this;
};
/**
* Define a `fmt` with the given `name`.
*
* @param {String} name
* @param {String|Function} fmt
* @return {Object} exports for chaining
* @api public
*/
exports.format = function(name, str){
exports[name] = str;
return this;
};
// default format
exports.format('default', ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"');
// short format
exports.format('short', ':remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms');
// tiny format
exports.format('tiny', ':method :url :status :res[content-length] - :response-time ms');
// dev (colored)
exports.format('dev', function(tokens, req, res){
var status = res.statusCode
, color = 32;
if (status >= 500) color = 31
else if (status >= 400) color = 33
else if (status >= 300) color = 36;
return '\033[90m' + req.method
+ ' ' + req.originalUrl + ' '
+ '\033[' + color + 'm' + res.statusCode
+ ' \033[90m'
+ (new Date - req._startTime)
+ 'ms\033[0m';
});
// request url
exports.token('url', function(req){
return req.originalUrl;
});
// request method
exports.token('method', function(req){
return req.method;
});
// response time in milliseconds
exports.token('response-time', function(req){
return new Date - req._startTime;
});
// UTC date
exports.token('date', function(){
return new Date().toUTCString();
});
// response status code
exports.token('status', function(req, res){
return res.statusCode;
});
// normalized referrer
exports.token('referrer', function(req){
return req.headers['referer'] || req.headers['referrer'];
});
// remote address
exports.token('remote-addr', function(req){
return req.socket && (req.socket.remoteAddress || (req.socket.socket && req.socket.socket.remoteAddress));
});
// HTTP version
exports.token('http-version', function(req){
return req.httpVersionMajor + '.' + req.httpVersionMinor;
});
// UA string
exports.token('user-agent', function(req){
return req.headers['user-agent'];
});
// request header
exports.token('req', function(req, res, field){
return req.headers[field.toLowerCase()];
});
// response header
exports.token('res', function(req, res, field){
return (res._headers || {})[field.toLowerCase()];
});

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