ability to build executables

This commit is contained in:
Craig Condon
2011-11-26 17:00:12 -06:00
parent 6f0534f433
commit c018684658
334 changed files with 27831 additions and 2 deletions
+2
View File
@@ -0,0 +1,2 @@
[commands]
proj = subl --project project.sublime-project
@@ -0,0 +1,2 @@
[commands]
proj = open project.tmproj
@@ -0,0 +1,2 @@
[commands]
proj = subl --project project.sublime-project
+2
View File
@@ -0,0 +1,2 @@
node_modules
n
+20
View File
@@ -0,0 +1,20 @@
Copyright (c) 2011 Craig Condon
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.
+392
View File
@@ -0,0 +1,392 @@
## Beanpole - Routing framework
### What are some features?
- Syntactic sugar (see below).
- Works with many protocols: amqp, http, websockets, etc.
- Hooking with other applications is a breeze with [daisy](https://github.com/spiceapps/daisy).
- Works well with coffeescript
### Projects using Beanpole
- [celeri](https://github.com/spiceapps/celeri) - CLI library
- [bonsai](https://github.com/spiceapps/bonsai) - application server
- [leche](https://github.com/spiceapps/leche) - Framework to build frontend / backend applications with the same code.
- [daisy](https://github.com/spiceapps/daisy) - Expose beanpole to: http, websockets, amqp (rabbitmq), etc.
- [beandocs](https://github.com/spiceapps/beandocs) - Generate documentation from your beanpole route comments.
- [beanprep](https://github.com/spiceapps/beanprep) - Scans beans in a given directory, and installs their dependencies.
- [cupboard](https://github.com/spiceapps/beanprep) - Reverse package manager.
### Beanpole ports
- [Actionscript](https://github.com/spiceapps/beanpole.as)
- [C++](https://github.com/spiceapps/beanpoll)
### Overview
![Alt ebnf diagram](http://i.imgur.com/v1wdO.png)
The basic route consists of a few parts: the `type` of route, and the `channel`. Here are some examples:
router.on('pull hello/:name', ...);
and
router.on('push hello/:name', ...);
#### Push Routes:
- Used to broadcast a message, or change (1 to many).
- Doesn't expect a response.
- Multiple listeners per route.
#### Pull Routes:
- Used to request data from a particular route (1 to 1).
- Expects a response.
- One listener per route.
- examples:
- request to http-exposed route
Using both `push`, and `pull` allows you to **bind** to a particular route. For example:
````javascript
var _numUsers = 0;
//numUser getter / setter function
function numUsers(value)
{
if(!arguments.length) return _numUsers;
_numUsers = value;
router.push('users/online', value);
}
//the request handler. This could be called in-app. It's also just as easily exposable as an API to http, websockets, etc.
router.on('pull users/online', function(request)
{
request.end(numUsers());
});
//pull num users initially, then listen for when num users changes.
router.on('push -pull users/online', function(response)
{
//handle change here..
console.log(response); //0, 3, 10...
});
//triggers above listener
numUsers(3);
numUsers(10);
````
Okay, so you might have noticed I added something funky here: `-pull` - that's a tag. Tags are structured like so:
router.on('pull -tagName hello/:route', ...);
or, you can add a value to it:
router.on('pull -method=GET hello/:route', ...);
As mentioned above, you can only have *one* listener per `pull` route. HOWEVER, you can have multiple listeners per `pull` route *if* you provide different tag values. For example:
````javascript
router.on({
/**
* returns the given user
*/
'pull -method=GET users/:userId': function(request)
{
//get the specific user
},
/**
* updates a user
*/
'pull -method=UPDATE users/:userId': function(request)
{
//update user here
},
/**
* deletes a user
*/
'pull -method=DELETE users/:userId': function(request)
{
//delete user
}
});
````
The above chunk of code is well suited for a REST-ful api without explicilty writing it *for* an http server. It can be used for any protocol. For example - say I wanted to *delete* a user using the code above:
````javascript
router.pull('users/' + someUserId, { tag: { method: 'DELETE'} }, function()
{
//delete user response
});
````
You might have guessed - tags can be used to filter routes. Okay, onto something a little more advanced: **middleware**. Here's an example:
````javascript
router.on({
/**
*/
'pull authorize': function(request)
{
if(request.data.secret != 'superSecret')
{
request.end('You shall not pass!');
}
else
{
//onto the next route
request.next();
}
},
/**
*/
'pull -method=GET authorize -> my/profile': function(request)
{
request.end('Super secret stuff!');
}
});
````
The token `->` denotes `my/profile` must go *through* the `authorize` route. Here are a few more use-cases:
````javascript
router.on({
/**
*/
'pull post/body': function(request)
{
//post http request body here. This is implemented in daisy
},
/**
*/
'pull session': function(request)
{
//initialize cookies for the user. Again, implemented in daisy
},
/**
*/
'pull -method=POST post/body -> session -> upload/video': function()
{
//passed through 2 routes before getting here.
},
/**
*/
'pull cache/:ttl': function(request)
{
//used to check if the *next* route is cached. If it is, then return the value vs continuing
},
/**
*/
'pull cache/10000 -> some/heavy/request': function(request)
{
//do some heavy stuff here, but go through the cache route so it's not called on each request
}
})
````
Middleware is especially useful for a REST-ful interface:
````javascript
router.on({
/**
* returns the given user
* @example /users/665468459
*/
'pull users/:userId': function(request)
{
getUser(request.data.postId, funciton(user)
{
request.user = user;
//route being used as middleware?
if(request.hasNext()) return request.next();
//return the user
request.end(user);
})
},
/**
* Returns a post made by a particular user
* @example /users/665468459/posts/54353499534
*/
'pull users/:userId -> users/:userId/posts/:postId': function(request)
{
getPosts(request.user, request.data.postId, function(posts)
{
request.end(posts);
})
}
});
````
Middleware can also be specified without using the token: `->`.An example:
````javascript
router.on({
/**
*/
'pull my/*': function()
{
//authorize user
},
/**
*/
'pull my/profile': function()
{
//goes through authorization first
}
});
````
Providing a wildcard `*` tells the router that **anything** after the route must go through it.
### Methods
#### router.on(type[,listener])
Listens to the given routes
- `type` - string or object. String would contain the route. Object would contain multiple routes / listeners
- `listener` - function listening to the route given.
#### router.push(route[, data][, options])
- `type` - the channel broadcast a message to.
- `data` - the data to push to the given route
- `options` - options for the given route
- `meta` - tags to use to filter out listeners
#### router.pull(route[, data][, options][, callback])
same as push, but expects a response
#### router.channels()
returns all registered channels
#### router.getRoute(route)
returns route expression
#### request.write(chunk)
Initializes a streamed response. Great for sending files
#### request.end([chunk])
Ends a response
#### request.hasNext()
Returns TRUE if there's a listener after the current one.
#### request.next()
Moves onto the next route.
#### request.forward(channel, callback)
Forwards the current request to the given channel
#### request.thru(channel[ ,options])
Treats the given channel as middleware
#### request.data
Data is added here
### One last goodie
Beanpole works well with coffeescript:
````coffeescript
router.on
#
'pull -method=GET say/hello': ->
"hello world!"
````
+185
View File
@@ -0,0 +1,185 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>currentDocument</key>
<string>lib/core/middleware/route/pushPull/pull/request.js</string>
<key>documents</key>
<array>
<dict>
<key>expanded</key>
<true/>
<key>name</key>
<string>beanpole</string>
<key>regexFolderFilter</key>
<string>!.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
<key>sourceDirectory</key>
<string></string>
</dict>
</array>
<key>fileHierarchyDrawerWidth</key>
<integer>200</integer>
<key>metaData</key>
<dict>
<key>examples/bean.hello/beans/hello.core/index.js</key>
<dict>
<key>caret</key>
<dict>
<key>column</key>
<integer>14</integer>
<key>line</key>
<integer>9</integer>
</dict>
<key>columnSelection</key>
<false/>
<key>firstVisibleColumn</key>
<integer>0</integer>
<key>firstVisibleLine</key>
<integer>0</integer>
<key>selectFrom</key>
<dict>
<key>column</key>
<integer>3</integer>
<key>line</key>
<integer>9</integer>
</dict>
<key>selectTo</key>
<dict>
<key>column</key>
<integer>14</integer>
<key>line</key>
<integer>9</integer>
</dict>
</dict>
<key>examples/bean.hook.hello/beans/test/index.js</key>
<dict>
<key>caret</key>
<dict>
<key>column</key>
<integer>0</integer>
<key>line</key>
<integer>0</integer>
</dict>
<key>firstVisibleColumn</key>
<integer>0</integer>
<key>firstVisibleLine</key>
<integer>0</integer>
</dict>
<key>examples/ws/server.js</key>
<dict>
<key>caret</key>
<dict>
<key>column</key>
<integer>14</integer>
<key>line</key>
<integer>34</integer>
</dict>
<key>columnSelection</key>
<false/>
<key>firstVisibleColumn</key>
<integer>0</integer>
<key>firstVisibleLine</key>
<integer>0</integer>
<key>selectFrom</key>
<dict>
<key>column</key>
<integer>3</integer>
<key>line</key>
<integer>34</integer>
</dict>
<key>selectTo</key>
<dict>
<key>column</key>
<integer>14</integer>
<key>line</key>
<integer>34</integer>
</dict>
</dict>
<key>lib/core/concrete/collection.js</key>
<dict>
<key>caret</key>
<dict>
<key>column</key>
<integer>0</integer>
<key>line</key>
<integer>13</integer>
</dict>
<key>firstVisibleColumn</key>
<integer>0</integer>
<key>firstVisibleLine</key>
<integer>0</integer>
</dict>
<key>lib/core/concrete/router.js</key>
<dict>
<key>caret</key>
<dict>
<key>column</key>
<integer>37</integer>
<key>line</key>
<integer>162</integer>
</dict>
<key>firstVisibleColumn</key>
<integer>0</integer>
<key>firstVisibleLine</key>
<integer>142</integer>
</dict>
<key>lib/core/controller.js</key>
<dict>
<key>caret</key>
<dict>
<key>column</key>
<integer>1</integer>
<key>line</key>
<integer>8</integer>
</dict>
<key>firstVisibleColumn</key>
<integer>0</integer>
<key>firstVisibleLine</key>
<integer>0</integer>
</dict>
<key>lib/core/middleware/meta/rotate.js</key>
<dict>
<key>caret</key>
<dict>
<key>column</key>
<integer>60</integer>
<key>line</key>
<integer>22</integer>
</dict>
<key>firstVisibleColumn</key>
<integer>0</integer>
<key>firstVisibleLine</key>
<integer>0</integer>
</dict>
<key>lib/core/middleware/route/pushPull/pull/request.js</key>
<dict>
<key>caret</key>
<dict>
<key>column</key>
<integer>0</integer>
<key>line</key>
<integer>14</integer>
</dict>
<key>firstVisibleColumn</key>
<integer>0</integer>
<key>firstVisibleLine</key>
<integer>0</integer>
</dict>
</dict>
<key>openDocuments</key>
<array>
<string>lib/core/controller.js</string>
<string>examples/bean.hello/beans/hello.core/index.js</string>
<string>examples/bean.hook.hello/beans/test/index.js</string>
<string>lib/core/concrete/collection.js</string>
<string>lib/core/concrete/router.js</string>
<string>lib/core/middleware/meta/rotate.js</string>
<string>lib/core/middleware/route/pushPull/pull/request.js</string>
<string>examples/ws/server.js</string>
</array>
<key>showFileHierarchyDrawer</key>
<true/>
<key>windowFrame</key>
<string>{{0, 4}, {1470, 1024}}</string>
</dict>
</plist>
+76
View File
@@ -0,0 +1,76 @@
### Beans
Beans are a fancy name for **plugin**.
### Organization
This is still very much a work in progress, but personally, I try to stick to a few conventions which help organize my "beans". Here are some general rules I follow:
- All beans are placed in a single directory. The directory name depends on what the application does.
- If the app serves one platform, then place the beans in `app/beans`;
- If the app serves multiple platforms, then place the beans in:
- `app/node/beans` for node.js specific beans.
- `app/web/beans` for web-specific beans.
- `app/shared/beans` for beans usable across all platforms.
- Bean names should reflect any RESTful API used in the bean.
- Beans in NPM have `bean` prepended to the name e.g: `bean.database.mongo`.
### Naming Conventions
Start off with the category of the bean first, and then the subject. A few examples:
- database.mongo
- database.redis
- database.mysql
Using `database` as the category tells that all beans share the same API. I can easily add / remove any `database` bean I want without breaking the application.
If you have a plugin that uses many plugins, then try this naming convention:
- `category`.core
And for beans that make up `category.core`:
- `category`.part.`subject`
For example:
- stream.core
- stream.part.facebook
- stream.part.twitter
- stream.part.google
Where all the **parts** make up `stream.core`. Remember that parts shouldn't do **anything**. They make-up core plugins. If you have a plugin that serves several plugins, split it up like so:
- posting.part.facebook
- friends.part.facebook
You could also do something like:
- stream.core
- group.core
- group.part.stream.core `part of stream.core`
Try and follow a RESTful naming convention. For example:
- stream.core
- stream.part.subscription.core `listening for streamed content, and sending off to registered subscribers`
- stream.part.subscription.email `subscription listening to stream, and sending a newsletter`
- stream.part.subscription.facebook `subscription listening to a stream, and posting out to facebook`
Note that `part` was dropped after `subscription`. I find it reduntant to use it after the first instance. We already know that `stream.part.subscription.core` is nothing without `stream.core`, so anything *after* that is also useless without the root plugin.
@@ -0,0 +1 @@
These notes are just so I can straighten out ideas about the architecture.
@@ -0,0 +1 @@
- modularized skyscraper
+155
View File
@@ -0,0 +1,155 @@
---------------------------------
Abstracting beanpole:
---------------------------------
Plugins *MUST NOT* know if whether a computer is networked. That job is delegated for *one* particular module: glue.core. If a client connects to a server, and contains a hook which acts like a plugin, the backend should treat it exactly as it would with a another networked server, or even internally. Glue.core would handle the handshake / authentication.
Likewise, connected servers using brazen must not handle communication with other servers differently than internally.
For security sake, each channel must provide metadata identifying whether it's private, protected, or public. Channels default to private if it's absent.
- Public channels are available to call without authentication
- Protected channels are available to call only by other crusted servers / clients
- Private channels can only be handled within the application.
All metadata is handled by the parser / router.
---------------------------------
Routing:
---------------------------------
The routing mechanism should parse syntactic sugar to identify how a particular channel is handled. Each channel must be separated by backslashes to allow for parameters. This is primarily for future implementation where beanpole might support HTTP requests.
'[type] [meta]* [channel_name]* [channel/:param] [additional]*'
- type: the type of channel - push, pull, ???
- meta: information attached to channel help glue.core, and other handlers.
- channel_name: the name of the channel. Private use from channel. Useful if channel needs to change, like a variable name.
- channel: the physical channel separated/by/backslashes
- additional: additional parameters specific to the channel type.
on push application/ready: function(data){}
'push private -pull application/ready'
on private pull of application ready passing through application exists:
'pull private application/exists -> application/ready': function(pull){ }
Calling back:
There needs to be a way for modules handling a particular request to identify where it's coming from.
---------------------------------
Types of channels:
---------------------------------
- pull - (getting) channels which respond to a particular request which maybe pushed out at a later time. This is from the source that makes the "push" with the same name.
- push - (setting) channels which are pushed out are used after a particular change has occurred. This is one to many.
Why I chose for push/pull to be different handlers with the same name:
Inspiration was actually taken back in the day when I was developing in Actionscript (I know, stfu). Bindable metadata was a slick way of getting a particular property from an object, and then sticking to it for any changes. For beanpole, "pull" would be the action of getting the current value of a particular "pod" (object), and "push" is the method of sticking to it.
Problems:
How do we distinguish single pulls from multiple pulls? For instance, for multiple pulls, I may want to "pull" stats from all the servers, but for a single pull, I may want to register a particular queue, and *only* send it to one instance.
Possible solutions:
on('pull multi…')
proxy.pull('multi…')
is a different channel handing than
on('pull…')
proxy.pull('…')
So technically, if I register
proxy.on('pull get.name', …')
proxy.on('pull multi get.name')
there wouldn't be any complaining, since I can call only one, or the other. Where the first one can be the *only* one, and the second one can have multiple. SO
proxy.pull('get.name')
would be the first one, and
proxy.pull('multi get.name')
would be the second one.
Multiple pulls also need to return to the requestor how many items are handling the request. This probably needs to take on a response, ondata, and end approach similar to node.js's streaming api
Streaming:
Without adding too much shit to the architecture, there may come a time where content is streamed to the particular requestor. This could be highly beneficial for programs which send files back and forth. Something which beanpole isn't necessarily equipped for at the moment. So, without changing the architecture, perhaps providing a method for supporting such a feature. For example:
This could be used:
proxy.pull('get.file','text.txt', {
data: function(buffer)
{
},
end: function()
{
}
});
or this could be used:
proxy.pull('get.file','text.txt',function(body)
{
});
But what about pulling from multiple sources?
proxy.pull('multi get.file', 'text.txt', function(source) //callback multiple times
{
source.on('data…
});
Hmmmm…
--------------------------------
Network Topology
--------------------------------
The architecture, mainly glue.core must have the ability to manage connected servers. Many of which should only communicate to particular applications. For instance: Currently there are two applications on spice.io which register queue's to the queue app . Each application has a slave which handles the cue, but the queue must know which slaves to send to. So:
- Each app must have an identifier shared amongst other apps it needs to communicate with (_appId)
- Since queues are protected, the handshake to between the queue app and the requester must be authenticated.
- The queue must receive the app id, and know what apps to send to via registered channels.
What if there are dozens of slaves running across a cluster of servers?
there *must* be a way for glue.core to use a load-balancing mechanism to send individual pulls to servers: round robin, least connected, etc. Stats from servers to see which is less busy?
What if there are multiple singleton pulls registered to glue.core?
Duh, fucking round-robin that shit when a pull-request is made.
+5
View File
@@ -0,0 +1,5 @@
#!/usr/bin/env node
var ebnf = require('ebnf-diagram');
ebnf.fromFile(__dirname + '/syntax.ebnf', __dirname + '/diagram.png',3000, 1654);
Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

+5
View File
@@ -0,0 +1,5 @@
"" {
syntax = ('push' | 'pull') { tag } channel { '->' channel }.
tag = '-' name [ '=' value ].
channel = '/' { [':'] name '/' } ['*'].
}
@@ -0,0 +1,18 @@
exports.plugin = function(mediator)
{
function init()
{
console.log('calling plugins to say hello...');
mediator.pull(' -multi say/hello', function(response)
{
console.log(response);
});
}
mediator.on({
'push init': init
});
}
@@ -0,0 +1,13 @@
exports.plugin = function(mediator)
{
function sayHello(pull)
{
pull.end('Hello Friend!');
}
mediator.on({
'pull -multi -public say/hello': sayHello
});
}
@@ -0,0 +1,13 @@
exports.plugin = function(mediator)
{
function sayHello(pull)
{
pull.end('Hello Neighbor!');
}
mediator.on({
'pull -multi -public say/hello': sayHello
})
}
@@ -0,0 +1,3 @@
var beanpole = require('../../lib/node');
beanpole.require(__dirname + '/beans').push('init');
@@ -0,0 +1,72 @@
var lazy = require('sk/core/lazy').callback;
exports.plugin = function(mediator)
{
var ops, sent = 0, received = 0, pulls = 0, capp = 0, interval;
function testSend(pull)
{
// console.log('received: %d', ++received);
/*if(received != sent)
{
console.log(recieved-sent)
}*/
pull.end(++received);
}
function pushHook(data, err, request)
{
console.success('starting test.');
timeout(data, ++capp, request);
}
function timeout(data, index, request)
{
var margin = 0;
setTimeout(function()
{
for(var i = ops.concurrent; i--;)
{
// console.log('sent %d', ++sent);
margin++;
request.from.pull('test/send', function(rcv)
{
// console.log('received: %d', rcv);
//wait till everything's done. Stop if anything's missed.
if(!(--margin))
{
console.log('#%d: done pulling %d requests from app #%d', ++pulls, ops.concurrent, index);
timeout(request, index);
}
});
}
}, ops.speed);
}
function init(op)
{
ops = op;
console.success('ready: speed=%d, concurrent=%d', ops.speed, ops.concurrent);
console.success('Now open another terminal window with the same script'.underline);
}
mediator.on({
'push init': init,
'pull -public test/send': testSend,
'push hook/connection': pushHook
});
}
@@ -0,0 +1,28 @@
var beanpole = require('../../lib/node'),
cli = require('sk/node/cli');
var ops = {};
process.stdout.write('num concurrent calls: ');
cli.next(function(arg)
{
ops.concurrent = Number(arg) || 10;
process.stdout.write('speed (ms): ');
cli.next(function(arg)
{
ops.speed = Number(arg) || 200;
beanpole.require(['hook.core','hook.http.mesh']).
require(__dirname + '/beans').push('init', ops);
})
})
//
@@ -0,0 +1,41 @@
var lazy = require('sk/core/lazy').callback;
exports.plugin = function(mediator)
{
var myName;
function pushSayHello(guestName)
{
console.log('hello %s!', guestName);
if(this.from)
{
this.from.push('say/hello/back', myName);
}
}
function pushSayHelloBack(guestName, err, push)
{
console.log('%s said hello back!', guestName);
}
function pushHook(data, err, push)
{
console.success('A person decided to join the partayyy.');
push.from.push('say/hello', myName)
}
function init(n)
{
myName = n;
pushSayHello(n);
}
mediator.on({
'push init': init,
'push hook/connection': pushHook,
'push -public say/hello': pushSayHello,
'push -public say/hello/back': pushSayHelloBack,
})
}
@@ -0,0 +1,26 @@
var beanpole = require('../../lib/node'),
argv = process.argv.concat();
argv.splice(0,2);
var name = argv[0];
function onName(name)
{
beanpole.require(['hook.core','hook.http.mesh']).
require(__dirname + '/beans').push('init', name.toString().replace('\n',''));
process.stdin.removeListener('data', onName);
};
if(name)
{
onName(name)
}
else
{
process.stdout.write('Name: ');
process.stdin.on('data', onName);
process.openStdin();
}
@@ -0,0 +1,41 @@
var lazy = require('sk/core/lazy').callback;
exports.plugin = function(mediator)
{
var myName;
function pushSayHello(guestName)
{
console.log('hello %s!', guestName);
if(this.from)
{
this.from.push('say/hello/back', myName);
}
}
function pushSayHelloBack(guestName, push)
{
console.log('%s said hello back!', guestName);
}
function pushHook(data, push)
{
console.success('A person decided to join the partayyy.');
push.from.push('say/hello', myName)
}
function init(n)
{
myName = n;
pushSayHello(n);
}
mediator.on({
'push init': init,
'push hook/connection': pushHook,
'push -public say/hello': pushSayHello,
'push -public say/hello/back': pushSayHelloBack,
})
}
@@ -0,0 +1,32 @@
var beanpole = require('../../lib/node'),
argv = process.argv.concat();
beanpole.require(['hook.http.mesh','hook.core']);
beanpole.on({
'pull -public hook2': function()
{
this.next();
},
'pull -public hook2 -> thru/hook2 -> test/hook': function()
{
console.log("fetching account...");
return "Successfuly passed through remote middleware";
},
'push -public hook2/ready': function()
{
console.log("Connected to hook 2");
beanpole.pull('test/hook', function(message)
{
console.success(message);
})
}
});
beanpole.push('init');
beanpole.push('ready','hook1');
console.log('hook1 is ready');
@@ -0,0 +1,32 @@
var beanpole = require('../../lib/node'),
argv = process.argv.concat();
beanpole.require(['hook.http.mesh','hook.core']);
beanpole.on({
'pull -public thru/hook2': function()
{
console.log("Through hook 2");
setTimeout(function(self)
{
console.success("done!")
self.next();
}, 500, this)
},
'push -public hook1/ready': function()
{
console.log("Connected to hook 1");
}
});
beanpole.push('init');
beanpole.push('ready','hook2');
console.log('hook2 is ready');
@@ -0,0 +1,26 @@
var beanpole = require('../../lib/node'),
argv = process.argv.concat();
beanpole.require(['hook.http.mesh','hook.core']);
beanpole.on({
'pull -public thru/hook3': function()
{
console.log('thru hook 3');
if(!this.next()) return "authenticated"
},
'push -public hook1/ready': function()
{
console.log("connected to hook 1");
}
});
beanpole.push('init');
beanpole.push('ready','hook2');
console.log('hook2 is ready');
@@ -0,0 +1,101 @@
var beanpole = require('../../lib/node').router(),
express = require('express'),
Url = require('url');
beanpole.require('middleware.core');
beanpole.on({
/**
*/
'pull -public basic/auth/root/pass -> basic/auth': function()
{
if(!this.next()) return 'authorized!';
},
/**
*/
'pull -public basic/auth -> auth/text': function()
{
return "authorized: " + this.user.name;
},
/**
*/
'pull -public session -> test/session': function()
{
var sess = this.session.data;
if(!sess.numVisits) sess.numVisits = 0;
sess.numVisits++;
return 'Num Visits: '+sess.numVisits;
},
/**
*/
'push init': function(init)
{
var srv = express.createServer(),
channels = beanpole.channels();
function initPath(path, expr)
{
srv.get('/' + path, function(req, res)
{
beanpole.pull(Url.parse(req.url).pathname, null, { meta: { stream: 1 }, req: req }, function(writer)
{
writer.on({
response: function(headers)
{
if(headers.session)
{
res.setHeader('Set-Cookie', headers.session.http)
}
if(headers.authorization)
{
res.statusCode = 401;
res.setHeader('WWW-Authenticate', headers.authorization.http);
}
},
write: function(data)
{
res.write(typeof data == 'object' ? JSON.stringify(data) : data);
},
end: function()
{
res.end();
}
});
})
});
}
for(var channel in channels)
{
var expr = channels[channel];
if(expr.type == 'pull' && expr.meta.public)
{
initPath(channel, expr);
}
}
srv.listen(8032);
}
});
beanpole.push('init');
@@ -0,0 +1,34 @@
exports.plugin = function(mediator)
{
function meta1(pull)
{
pull.end('metadata 1');
}
function meta2(pull)
{
pull.end('metadata 2');
}
function meta3(pull)
{
pull.end('metadata 3');
}
function filterMeta(pull)
{
mediator.pull('meta', null, { meta: { group: pull.data.group } }, function(data)
{
pull.end(data || '');
});
}
mediator.on({
'pull -public -rotate -group=1 meta': meta1,
'pull -public -rotate -group=2 meta': meta2,
'pull -public -rotate -group=3 meta': meta3,
'pull -public meta/:group': filterMeta
});
}
@@ -0,0 +1,41 @@
var express = require('express');
exports.plugin = function(mediator)
{
function init(pull)
{
var srv = express.createServer();
mediator.pull('hook', function(data)
{
data.channels.forEach(function(channel)
{
srv.get('/'+channel.name, function(req, res)
{
var name = channel.name;
for(var param in req.params)
{
name = name.replace(':'+param,req.params[param])
}
mediator.pull('-stream ' + name, function(writer)
{
writer.pipe(res);
})
});
});
srv.listen(8032);
});
}
console.log('Server running on port ' + 8032+'. try http://localhost:8032/meta/[1,2,3], or omit the number and watch them rotate.');
mediator.on({
'push init': init
});
}
@@ -0,0 +1,3 @@
var beanpole = require('../../lib/node');
beanpole.require(['hook.core','hook.http.mesh']).require(__dirname + '/beans').push('init');
@@ -0,0 +1,46 @@
exports.plugin = function(mediator)
{
function init()
{
var i = 0;
//default
mediator.pull('load/site', { site: 'http://www.google.com/' }, function(chunk)
{
console.log('http://www.google.com chunk #%d', i++);
});
//letting brazilnut handle the combining of buffers
mediator.pull('-batch load/site', { site: 'http://www.engadget.com/' }, function(buffer)
{
console.log('done reading http://engadget.com with %d chunks.', buffer.length);
});
//streaming data
mediator.pull('-stream load/site', { site: 'http://www.google.com/' }, function(reader)
{
var buffer = [];
reader.on({
write: function(chunk)
{
buffer.push(chunk)
},
end: function()
{
console.log('done reading http://google.com with %d chunks.', buffer.length);
}
})
});
}
mediator.on({
'push init': init
});
}
@@ -0,0 +1,3 @@
var brazilnut = require('../../lib/node');
brazilnut.require(__dirname + '/beans').push('init');
@@ -0,0 +1,32 @@
var beanpole = require('../../lib/node').router(),
Structr = require('structr');
function hello()
{
return "hello!.";
}
beanpole.on({
'pull hello/silly/world': hello
})
var start = new Date();
for(var i = 10000; i--;)
{
beanpole.pull('hello/silly/world', function (res)
{
// console.log(res)
});
}
console.log(new Date().getTime() - start.getTime());
+49
View File
@@ -0,0 +1,49 @@
var beanpole = require('../../lib/node').router();
function all()
{
console.log('all');
if(!this.next())
{
console.log("DONE!")
}
}
function all2()
{
console.log("ROTATE")
}
function thru()
{
console.log('thru');
if(!this.next())
{
console.log("CANNOT CONTINUE!")
}
}
function thru2()
{
console.log("THRU 2");
this.next();
}
function sayHi(message)
{
console.log(message);
}
beanpole.on({
'dispatch /*': all,
// 'dispatch -d /*': all2,
'dispatch thru2': thru2,
'dispatch thru2 -> thru': thru,
'dispatch thru2 -> thru -> thru -> hello': sayHi
});
// beanpole.dispatch('hello','world!');
// beanpole.dispatch('hello','world!');
beanpole.dispatch('hello','world!');
@@ -0,0 +1,6 @@
exports.plugin = function(mediator)
{
}
@@ -0,0 +1,75 @@
var beanpole = require('../../lib/node').router();
function hello1(pull)
{
return "hello 1!";
}
function hello2(pull)
{
return "hello 2!";
}
function hello3(pull)
{
return "hello 3!";
}
function init()
{
//hello 3!
beanpole.pull('say/hello', function(msg)
{
console.log(msg)
});
//hello 1!
beanpole.pull('say/hello', function(msg)
{
console.log(msg)
});
//hello 2!
beanpole.pull('say/hello', function(msg)
{
console.log(msg)
});
//hello 2!
beanpole.pull('-name=group1 say/hello', function(msg)
{
console.log(msg)
});
//hello 3!
beanpole.pull('-name=group1 say/hello', function(msg)
{
console.log(msg)
});
//hello 1!
beanpole.pull('-name=group2 say/hello', function(msg)
{
console.log(msg)
});
//hello 1!
beanpole.pull('-name=group2 say/hello', function(msg)
{
console.log(msg)
});
}
beanpole.on({
'push init': init,
//NOTE: rotate=N is provided just so the properties aren't overridden
'pull -name=group2 -rotate=3 say/hello': hello1,
'pull -name=group1 -rotate=2 say/hello': hello2,
'pull -name=group1 -rotate=1 say/hello': hello3
});
beanpole.push('init');
@@ -0,0 +1,53 @@
var beanpole = require('../../lib/node').router();
function groupHello1(pull)
{
return "hello!";
}
function groupHello2(pull)
{
return "hello again!";
}
function groupHello3(pull)
{
return "hello for a third time!";
}
function init()
{
//hello!
//hello again!
//hello for a third time!
beanpole.pull('-multi say/hello', function(msg)
{
console.log(msg)
});
//hello!
//hello again!
beanpole.pull('-multi -name=group1 say/hello', function(msg)
{
console.log(msg)
});
//hello for a third time!
beanpole.pull('-multi -name=group2 say/hello', function(msg)
{
console.log(msg)
});
}
beanpole.on({
'push init': init,
//-multi=N is set so props aren't overridden
'pull -name=group2 -multi=3 say/hello': groupHello3,
'pull -name=group1 -multi=2 say/hello': groupHello2,
'pull -name=group1 -multi=1 say/hello': groupHello1
});
beanpole.push('init')
@@ -0,0 +1,33 @@
var beanpole = require('../../lib/node');
var router = beanpole.router();
router.params({
'http.gateway': {
http:{
port: 8080
}
}
});
router.on({
'push -public spice.io/theurld/ready': function()
{
console.log("READY");
}
});
router.require(['http.server', 'http.gateway','hook.http','hook.core']);
router.push('init');
router.push('hook/add','http://theurld.spice.io');
@@ -0,0 +1,43 @@
var beanpole = require('../../lib/node');
var router = beanpole.router();
router.params({
'http.gateway': {
http:{
port: 8081
}
}
});
router.on({
'pull -http hello': function()
{
return "Hello world!";
},
'push -public head/ready': function()
{
console.log("READ READY");
},
'push -public hook/ready': function()
{
console.log("LOL HOOKKS");
}
});
router.require(['http.server', 'http.gateway','hook.http.mesh2','hook.core']);
router.push('init');
router.push('ready','hook');
router.pull('hooks/add', 'http://localhost:8080', function(){});
@@ -0,0 +1,38 @@
var beanpole = require('../../lib/node');
var router = beanpole.router();
router.params({
'http.gateway': {
http:{
port: 8082
}
}
});
router.on({
'pull -http hello': function()
{
return "Hello world!";
},
'push -public head/ready': function()
{
console.log("READ READY");
}
});
router.require(['http.server', 'http.gateway','hook.http.mesh2','hook.core']);
router.push('init');
router.push('ready','hook');
router.pull('hooks/add', 'http://localhost:8080', function(){});
@@ -0,0 +1,43 @@
var beanpole = require('../../lib/node');
var router = beanpole.router();
router.params({
'http.gateway': {
http:{
port: 8083
}
}
});
router.on({
'pull -http hello': function()
{
return "Hello world!";
},
'push -public head/ready': function()
{
console.log("READ READY");
},
'push -public hook/ready': function()
{
console.log("LOL HOOKKS");
}
});
router.require(['hook.http.mesh','hook.core']);
router.push('init');
router.push('ready','hook');
router.pull('hooks/add', 'http://localhost:8080', function(){});
@@ -0,0 +1,48 @@
var beanpole = require('../../lib/node');
var router = beanpole.router();
router.params({
'http.gateway': {
http:{
port: 8083
}
}
});
var id = Date.now();
console.log(id);
router.on({
'pull -http hello': function()
{
return "Hello world!";
},
'push -public sibling/ready': function(id)
{
console.log("SIBLING READY: " + id);
},
'push -public hook/ready': function()
{
if(this.from == router) return;
this.from.push('sibling/ready', id);
}
});
router.require(['hook.http.mesh','hook.core']);
router.push('init');
router.push('ready','hook');
@@ -0,0 +1,41 @@
var beanpole = require('../../lib/node');
var router = beanpole.router();
router.params({
'http.gateway': {
http:{
port: 8080
}
}
});
router.on({
'pull -http hello': function()
{
return "Hello world!";
},
'push -public hook/ready': function()
{
console.log("READY");
},
'push -public duck': function()
{
console.log("DUCK!");
}
});
router.require(['http.server', 'http.gateway','hook.http.mesh2','hook.core']);
router.push('init');
router.push('ready','head');
@@ -0,0 +1,31 @@
var beanpole = require('../../lib/node').router();
function intercept(pull)
{
//change the data
pull.data.sport = 'soccer';
pull.next();
}
function test(pull)
{
pull.end(pull.data.sport)
}
function init()
{
beanpole.pull('test', { sport: 'football' }, function(result)
{
console.log(result)
});
}
beanpole.on({
'push init': init,
'pull -intercept=sport te`st/interception': intercept,
'pull test': test
})
beanpole.push('init');
@@ -0,0 +1,20 @@
var beanpole = require('../../lib/node'),
argv = process.argv.concat();
var router = beanpole.router(),
router2 = beanpole.router();
var listen = {
'push hello': function()
{
console.log('hello!')
}
}
router.on(listen).dispose();
router2.on(listen);
router.push('hello')
+32
View File
@@ -0,0 +1,32 @@
var beanpole = require('../../lib/node').router();
function delay()
{
setTimeout(function (self){ self.next(); }, this.data.seconds * 1000, this);
}
beanpole.on({
'push -filter=BOMB (say/hello or say/hello/world) or blah': function()
{
console.log("hello world!")
},
'push -filter=GOLD blah': function()
{
console.log("GO")
},
'pull /*': function()
{
console.log("G")
}
})
beanpole.push('say/hello');
beanpole.push('say/hello/world');
beanpole.push('-filter=GOLD blah');
beanpole.push('-filter=BOMB blah');
beanpole.push('/');
@@ -0,0 +1,21 @@
var beanpole = require('../../lib/node').router();
beanpole.on({
/**
*/
'pull -overridable say/hello': function()
{
return "hello";
},
'pull say/hello': function()
{
return "hello world!"
}
});
+61
View File
@@ -0,0 +1,61 @@
var beanpole = require('../../lib/node').router();
function sayHi()
{
console.log(this.data)
console.log('hi')
// this.next();
if(!this.next()) return "don't pass " + this.data.name+ " "+this.data.last;
}
function sayHi2()
{
console.log('pass!');
if(!this.next())
{
this.end("GO")
}
}
function sayHiCraig()
{
if(!this.next()) return "GO"
}
function sayHi3()
{
if(!this.next()) return "hello";
}
function init()
{
beanpole.pull('hello2/craig', function(result)
{
console.log(result)
});
}
function test2()
{
}
beanpole.on({
'push init': init,
'pull /*': function()
{
console.log("PSSS") ;
this.next();
},
'pull hellotest': sayHi2,
'pull hellotest -> hellotest -> hello2/:name': sayHi3
});
// console.log(beanpole.routeMiddleware._routers.pull._collection._routes)
beanpole.push('init');
+46
View File
@@ -0,0 +1,46 @@
var beanpole = require('../../lib/node').router();
beanpole.on({
'pull name': function()
{
setTimeout(function()
{
beanpole.push('name', function()
{
return "SLAYERS!";
});
beanpole.push('name', 'PUSH');
// beanpole.push('name', 'PUSH');
// beanpole.push('name', 'PUSH');
// beanpole.push('name', 'PUSH');
// beanpole.push('name', 'PUSH');
}, 100);
return 'PULL';
},
'push /*': function()
{
console.log("PUSH INITIALIZED!");
this.next();
},
'push through': function(data)
{
console.log("delay!")
setTimeout(function(self){ self.next(); }, 500, this)
},
'push -pull name': function(name)
{
console.log(name)
},
'push -pull name': function(name)
{
console.log(name)
},
/*'push name': function(name)
{
console.log(name +' PUSH')
}*/
});
+35
View File
@@ -0,0 +1,35 @@
var beanpole = require('../../lib/node').router();
function sayHi2()
{
console.log('pass!');
// return 'ga'
if(!this.next())
{
console.log("FAIL")
}
}
function sayHi3()
{
console.log(this.data)
}
function init()
{
beanpole.push('hello3', 'craig!');
}
beanpole.on({
'push init': init,
'push hello2 -> hello2 -> hello2 -> hello2 -> hello2 -> hello2 -> hello': sayHi2,
'push hello2': sayHi2,
'push hello -> hello2 -> hello3': sayHi3
})
beanpole.push('init');
@@ -0,0 +1,43 @@
var beanpole = require('../../lib/node').router();
function delay()
{
setTimeout(function (self){ self.next(); }, this.data.seconds * 1000, this);
}
function sayHi()
{
return "I.";
}
function sayHi2()
{
return "Love.";
}
function sayHi3()
{
return "Coffee.";
}
function init()
{
for(var i = 3; i--;)
{
beanpole.pull('say/hi', function (res)
{
console.log(res)
});
}
}
beanpole.on({
'push init': init,
'pull delay/:seconds': delay,
'pull -rotate delay/1 -> say/hi': sayHi,
'pull -rotate delay/2 -> say/hi': sayHi2,
'pull -rotate delay/3 -> say/hi': sayHi3
})
beanpole.push('init');
+87
View File
@@ -0,0 +1,87 @@
var beanpole = require('../../lib/node');
var router = beanpole.router();
router.on({
/**
*/
'pull /*': function()
{
console.log("THRU");
this.next();
},
/**
*/
'pull -two /*': function()
{
console.log("THRU MOO");
this.next();
},
/**
*/
'pull -two /hello/*': function()
{
console.log("THRU hoo");
this.next();
},
/**
*/
'pull /hello/param/*': function()
{
console.log("THRU AGAIN");
this.next();
},
/**
*/
'pull -t /hello/param/*': function()
{
console.log("THRU AGAIN");
this.next();
},
/**
*/
'pull -t2 /hello/param/*': function()
{
console.log("THRU AGAIN");
this.next();
},
/**
*/
'pull -t3 /hello/param/*': function()
{
console.log("THRU AGAIN");
this.next();
},
/**
*/
'pull hello/param': function()
{
console.log("HELLO!");
}
});
router.pull('hello/param', function()
{
});
@@ -0,0 +1,9 @@
<html>
<head>
<title> Socket Test </title>
<script src="http://localhost:6032/socket.io/socket.io.js" type="text/javascript"></script>
<script src="index.js" type="text/javascript"> </script>
</head>
<body>
</body>
</html>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,9 @@
<html>
<head>
<title> Socket Test </title>
<script src="http://localhost:6032/socket.io/socket.io.js" type="text/javascript"></script>
<script src="index.js" type="text/javascript"> </script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,65 @@
var beanpole = require('../../../../lib/core').router();
function pluginExample(router)
{
var name = prompt("What's your name?", 'craig');
var time = Number(prompt("When do you want an alert? (in seconds)", 1));
function appendBody(message)
{
var div = document.createElement('div');
div.innerHTML = message;
document.body.appendChild(div);
}
router.on({
/**
*/
'pull -public some/random/callback': function(request)
{
appendBody(request.data.message);
this.from.push('notify/clients', request.data);
request.end();
},
/**
*/
'push send/message': function(data)
{
beanpole.push('call/later', { channel: 'some/random/callback', data: { _id: new Date().getTime(), message: data.message }, sendAt: new Date().getTime() + data.delay})
},
/**
*/
'push -public notify/clients': function(data)
{
appendBody('notified from another client: '+ data.message);
}
});
beanpole.on({
'push -public -one spice.io/ready': function()
{
beanpole.push('send/message', { message: "hello " + name +"!", name: name, delay: time * 1000});
}
});
}
require('../../../../lib/core/beans/hook.core').plugin(beanpole);
require('../../../../lib/web/beans/hook.socket.io.client').plugin(beanpole);
pluginExample(beanpole);
beanpole.push('ready','client');
beanpole.push('init');
+37
View File
@@ -0,0 +1,37 @@
var beanpole = require('../../lib/node').router();
beanpole.require(['hook.core','hook.http.mesh']);
beanpole.on({
/**
*/
'push init': function()
{
beanpole.push('ready', 'spice.io');
},
/**
*/
'push -public call/later': function(data)
{
console.log('calling later');
var self = this;
setTimeout(function()
{
self.from.pull(data.channel, data.data, { inner: self.inner }, function(result)
{
console.log(result)
})
},Math.max(0, data.sendAt - new Date().getTime()));
// setTimeout()
}
});
beanpole.push('init');
+40
View File
@@ -0,0 +1,40 @@
var beanpole = require('../../lib/node').router();
beanpole.require(['hook.core','hook.socket.io.server','hook.http.mesh']);
beanpole.on({
/**
*/
'push init': function()
{
beanpole.push('ready', 'spice.io');
},
/**
*/
'pull -public say/hello': function()
{
return "hello MADRE!";
},
/**
*/
'push -public client/ready': function()
{
console.log("CLIENT READY");
this.from.pull('get/name', function(name)
{
console.log(name)
})
}
});
beanpole.push('init');
@@ -0,0 +1,338 @@
var Structr = require('structr'),
Parser = require('./parser'),
utils = require('./utils'),
Request = require('./request'),
Janitor = require('sk/core/garbage').Janitor;
/**
* collection for routes
*/
/**
* IMPORTANT notes regarding this class
* 1. you can have multiple explicit middleware (/path/*)
*/
var Collection = Structr({
/**
* Constructor. What else do you think it is?
*/
'__construct': function(ops)
{
//the options for the router
this._ops = ops || {};
//these are the channels parsed into a traversable route
this._routes = this._newRoute();
//these get executed whenever there's a new "on"
this._middleware = this._newRoute();
//the current route index. increments on every route!
this._routeIndex = 0;
},
/**
*/
'has': function(expr)
{
var routes = this.routes(expr);
for(var i = routes.length; i--;)
{
if(routes[i].target) return true;
}
return false;
},
/**
*/
'route': function(channel)
{
return this._route(channel.paths);
},
/**
*/
'routes': function(expr)
{
var channels = expr.channels,
routes = [];
for(var i = channels.length; i--;)
{
routes.push(this.route(channels[i]));
}
return routes;
},
/**
* listens to the given expression for any chandage
*/
'add': function(expr, callback)
{
var janitor = new Janitor();
for(var i = expr.channels.length; i--;)
{
janitor.addDisposable(this._add(expr.channels[i], expr.meta, callback));
}
return janitor;
},
/**
*/
'_add': function(channel, meta, callback)
{
var paths = channel.paths,
isMiddleware = channel.isMiddleware,
middleware = channel.thru,
//middleware isn't used explicitly. Rather, it's *injected* into the routes which ARE used. Remember that.
//explicit middleware looks like some/path/*
currentRoute = this._start(paths, isMiddleware ? this._middleware : this._routes, true);
//some explicit middleware might already be defined, so we need to get the *one* to pass through.
var before = this._before(paths, currentRoute);
if(middleware) this._endMiddleware(middleware).thru = before;
//the final callback for the route
var listener = {
callback: callback,
//metadata for the expression
meta: meta,
//keeps tabs for later use (in request)
id: 'r'+(this._routeIndex++),
//this is a queue where the first item is executed first, then on until we reach the last item
thru: middleware || before,
isMiddleware: channel.isMiddleware,
path: paths,
dispose: function()
{
var i = currentRoute.listeners.indexOf(listener);
if(i > -1) currentRoute.listeners.splice(i, 1);
}
};
currentRoute.meta = Structr.copy(meta, currentRoute.meta);
//at this point we can inject the listener into the current route IF it's middleware.
if(isMiddleware) this._injectMiddleware(listener, paths);
//now that we're in the clear, need to add the listener!
if(!currentRoute.listeners) currentRoute.listeners = [];
//now to add it. Please take remember, for MOST CASES, "_listeners" will only have one, especially for http / requests
currentRoute.listeners.push(listener);
//the return statement allows for the item to be disposed of
return listener;
},
/**
*/
'_endMiddleware': function(target)
{
var current = target || {};
while(current.thru)
{
current = current.thru;
}
return current;
},
/**
* injects explicit middleware (/path/*) in all the routes which go through its path
*/
'_injectMiddleware': function(listener, paths)
{
//level is only important for
listener.level = paths.length;
//need to go through *all* routes ~ even middleware, because middleware also have
//routes to pass through ~ Inception.
var afterListeners = this._after(paths, this._routes).concat(this._after(paths, this._middleware));
//go through ALL items to put before this route, but make sure the item we're replacing isn't higher
//in the middleware chain, because higher methods will already *have* reference to this pass-thru
for(var i = afterListeners.length; i--;)
{
var currentListener = afterListeners[i];
var currentMiddleware = currentListener.thru,
previousMiddleware = currentListener;
while(currentMiddleware)
{
if(currentMiddleware.level != undefined)
{
if(currentMiddleware.level < listener.level)
{
previousMiddleware.thru = listener;
}
break;
}
previousMiddleware = currentMiddleware;
currentMiddleware = currentMiddleware.thru;
}
if(!currentMiddleware) previousMiddleware.thru = listener;
}
},
/**
* reveals routes which must come *before* a middleware
* after beats circular references
* TODO: following code is __ugly as fuck__.
*/
'_before': function(paths, after)
{
var current = this._middleware,
listeners = [];
for(var i = 0, n = paths.length; i < n; i++)
{
//this makes sure we don't get to the end for pass thrus
if(current.listeners) listeners = current.listeners;
var path = paths[i],
newCurrent = path.param ? current._route._param : current._route[path.name];
if(current == after || !newCurrent) break;
current = newCurrent;
}
// if(current != after)
//this is a check against pass thrus to beat circular references. It *also* allows this: hello/* -> hello
if(current != after && current.listeners) listeners = current.listeners;
return listeners[0];
},
/**
* reveals everyhing that comes *after* a route (for pass-thru's)
*/
'_after': function(paths, routes)
{
return this._flatten(this._start(paths, routes));
},
/**
* returns the starting point of a route
*/
'_route': function(paths, routes, create, retControl)
{
var control = (routes || this._routes),
current = control._route;
for(var i = 0, n = paths.length; i < n; i++)
{
var path = paths[i],
name = path.param ? '_param' : path.name;
if(!current[name] && create)
{
current[name] = this._newRoute(i);
}
if(current[name])
{
current = current[name];
}
else
{
current = current._param;
}
if(!current) return {};
control = current;
current = current._route;
}
return control;
},
/**
*/
'_start': function(paths, routes)
{
return this._route(paths, routes, true);
},
/**
*/
'_newRoute': function(level)
{
return { _route: { }, _level: level || 0 };
},
/**
* flattens all routes into a single array
*/
'_flatten': function(route)
{
var listeners = route.listeners ? route.listeners.concat() : [];
for(var path in route._route)
{
listeners = listeners.concat(this._flatten(route[path] || {}));
}
return listeners;
}
});
module.exports = Collection;
+595
View File
@@ -0,0 +1,595 @@
var Structr = require('structr');
/**
* parses syntactic sugar.
Why the hell are you using a parser for something so simple? Because I wanted to. Yeah, I could have done it in Regexp, but fuck that >.>... This
Is much more fun.
*/
//follow the pattern below when adding tokens plz.
var Token = {
// A-Z
WORD: 1,
// -metadata
METADATA: 1 << 1,
// =
PATH: 1 << 2,
// :param
PARAM: 1 << 3,
// ->
TO: 1 << 4,
// for routing
BACKSLASH: 1 << 5,
// .
DOT: 1 << 6,
// * - this is an auto-middleware
STAR: 1 << 7,
// "or"
OR: 1 << 8,
// (
LP: 1 << 9,
// )
RP: 1 << 10,
// =
EQ: 1 << 11,
//whitespace
WHITESPACE: 1 << 12
};
//reserved keywords
var Reversed = {
or: Token.OR
}
var Tokenizer = function()
{
//source of the string to tokenize
var source = '',
//the position of the parser
pos = 0,
//the current token
currentToken,
self = this;
/**
* getter / setter for the source
*/
this.source = function(value)
{
if(value)
{
source = value+' '; //padding
pos = 0;
}
return source;
}
/**
* next token
*/
this.next = function(keepWhite)
{
return currentToken = nextToken(keepWhite);
}
/**
*/
this.peekChars = function(n)
{
return source.substr(pos, n);
}
/**
*/
this.current = function(keepWhite)
{
return currentToken || self.next(keepWhite);
}
/**
*/
this.position = function()
{
return pos;
}
/**
*/
var nextToken = function(keepWhite, ignoreError)
{
if(!keepWhite) skipWhite();
if(eof()) return null;
var c = currentChar(), ccode = c.charCodeAt(0);
if(isWhite(ccode))
{
skipWhite();
return token(' ',Token.WHITESPACE);
}
//a-z0-9
if(isAlpha(ccode))
{
var w = nextPath();
return token(w, Reversed[w.toLowerCase()] || Token.WORD);
}
switch(c)
{
//for middleware
case '-':
if(nextChar() == '>') return token('->', Token.TO, true);
if(isAlpha(currentCharCode())) return token(nextPath(), Token.METADATA);
error();
//parameters for routes
case ':':
if(isAlpha(nextCharCode())) return token(nextPath(), Token.PARAM);
error();
case '/': return token('/', Token.BACKSLASH, true);
case '.': return token('.', Token.DOT, true);
case '*': return token('*', Token.STAR, true);
case '(': return token('(', Token.LP, true);
case ')': return token(')', Token.RP, true);
case '=': return token('=', Token.EQ, true);
//path part
default: return token(nextPath(), Token.PATH);
}
//eof
return null;
}
var error = function()
{
throw new Error('Unexpected character "'+currentChar()+'" at position '+pos+' in "'+source+'"');
}
/**
*/
var token = function(value, type, skipOne)
{
if(skipOne) nextChar();
return { value: value, type: type };
}
/**
*/
var nextChar = this.nextChar = function()
{
return source[++pos];
}
/**
*/
var currentChar = this.currentChar = function()
{
return source[pos];
}
/**
*/
var isAlpha = this.isAlpha = function(c)
{
return (c > 96 && c < 123) || (c > 64 && c < 91) || isNumber(c) || c == 95;
}
/**
*/
var isWhite = this.isWhite = function(c)
{
return c == 32 || c == 9 || c == 10;
}
/**
*/
var isNumber = this.isNumber = function(c)
{
return c > 47 && c < 58;
}
/**
*/
var nextCharCode = function()
{
return nextChar().charCodeAt(0);
}
/**
*/
var currentCharCode = function()
{
return currentChar().charCodeAt(0);
}
/**
*/
var rewind = function(steps)
{
pos -= (steps || 1);
}
/**
*/
var skipWhite = function()
{
var end = false;
while(!(end = eof()))
{
if(!isWhite(currentCharCode())) break;
nextChar();
}
return !end;
}
/**
*/
var nextNumber = function()
{
var buffer = currentChar();
while(!eof())
{
if(isNumber(nextCharCode()))
{
buffer += currentChar();
}
else
{
break;
}
}
return buffer;
}
/**
*/
var nextPath = function()
{
var buffer = currentChar();
while(!eof())
{
if(!isWhite(nextCharCode()) && !currentChar().match(/[\/=()]/g))
// if(isAlpha(nextCharCode()) || isNumber(currentCharCode()))
{
buffer += currentChar();
}
else
{
break;
}
}
return buffer;
}
/**
* end of file
*/
var eof = function()
{
return pos > source.length-2;
}
}
var ChannelParser = function()
{
var tokenizer = new Tokenizer(),
cache = {};
/**
* parses a string into a handleable expression
*/
this.parse = function(source)
{
if(!source) throw new Error('Source is not defined');
//stuff might have happened to the expression, so we need to clone it. it DEFINITELY changes
//when pull requests are made...
if(cache[source]) return Structr.copy(cache[source]);
tokenizer.source(source);
return Structr.copy(cache[source] = rootExpr());
}
var rootExpr = function()
{
var expr = tokenizer.current(),
type,
meta = {};
//type is not defined, but that's okay!
if(expr.type == Token.WORD && tokenizer.isWhite(tokenizer.peekChars(1).charCodeAt(0)) && tokenizer.position() < tokenizer.source().length-1)
{
type = expr.value;
tokenizer.next();
}
var token, channels = [];
while(token = tokenizer.current())
{
switch(token.type)
{
//-metadata=test
case Token.METADATA:
meta[token.value] = metadataValue();
break;
case Token.BACKSLASH:
case Token.WORD:
case Token.STAR:
channels = channels.concat(channelsExpr());
break;
case Token.OR:
tokenizer.next();
break;
default:
tokenizer.next();
break;
}
}
return { type: type, meta: meta, channels: channels };
}
var metadataValue = function()
{
if(tokenizer.currentChar() == '=')
{
tokenizer.next();
var v = tokenizer.next().value;
tokenizer.next();
return v;
}
tokenizer.next();
return 1;
}
var channelsExpr = function()
{
var channels = [],
to;
while(hasNext())
{
if(currentTypeIs(Token.LP))
{
tokenizer.next();
}
if(currentTypeIs(Token.WORD | Token.PARAM | Token.STAR | Token.BACKSLASH))
{
channels.push([channelPathsExpr()]);
while(currentTypeIs(Token.OR))
{
tokenizer.next();
channels[channels.length-1].push(channelPathsExpr());
}
}
else
{
break;
}
if(currentTypeIs(Token.RP))
{
tokenizer.next();
}
if(currentTypeIs(Token.TO))
{
tokenizer.next();
}
}
var _orChannels = splitChannelExpr(channels.concat(), []),
channelsThru = [];
for(var i = _orChannels.length; i--;)
{
var chain = Structr.copy(_orChannels[i]),
current = channel = chain[chain.length-1];
for(var j = chain.length-1; j--;)
{
current = current.thru = chain[j];
}
channelsThru.push(channel);
}
return channelsThru;
}
var splitChannelExpr = function(orChannels, stack)
{
if(!orChannels.length) return [stack];
var current = orChannels.shift();
if(current.length == 1)
{
stack.push(current[0]);
return splitChannelExpr(orChannels, stack);
}
else
{
var split = [];
for(var i = current.length; i--;)
{
var stack2 = stack.concat();
stack2.push(current[i]);
split = split.concat(splitChannelExpr(orChannels.concat(), stack2));
}
return split;
}
}
var channelPathsExpr = function(type)
{
var paths = [],
token,
isMiddleware = false,
cont = true;
while(cont && (token = tokenizer.current()))
{
switch(token.type)
{
case Token.WORD:
case Token.PARAM:
case Token.PATH:
paths.push({ name: token.value, param: token.type == Token.PARAM });
break;
case Token.BACKSLASH:
break;
default:
cont = false;
break;
}
if(cont) tokenizer.next();
}
if(currentTypeIs(Token.STAR))
{
isMiddleware = true;
tokenizer.next();
}
return { paths: paths, isMiddleware: isMiddleware };
}
var currentToken = function(type, igError)
{
return checkToken(tokenizer.current(), type, igError);
}
var nextToken = function(type, igError, keepWhite)
{
return checkToken(tokenizer.next(keepWhite), type, igError);
}
var checkToken = function(token, type, igError)
{
if(!token || !(type & token.type))
{
if(!igError) throw new Error('Unexpected token "'+(token || {}).value+'" at position '+tokenizer.position()+' in '+tokenizer.source());
return null;
}
return token;
}
var currentTypeIs = function(type)
{
var current = tokenizer.current();
return current && !!(type & current.type);
}
var hasNext = function()
{
return !!tokenizer.current();
}
}
exports.parse = new ChannelParser().parse;
+310
View File
@@ -0,0 +1,310 @@
var Structr = require('structr'),
Parser = require('./parser'),
utils = require('./utils');
var Request = Structr({
/**
*/
'__construct': function(listener, batch)
{
//data necessary for the request
this.data = batch.data;
//inner data which is invisible to the request, but contains data which needs to get passed along
this.inner = batch.inner;
//the end callback
this.callback = batch.callback;
this._used = {};
this._queue = [];
//yes, and I know what you're thinking: why the hell are you copying data?
//Well, we work backwards, and we don't know if parameters might override data passed to the current URI - we need to be prepared for that. SO
//as we're working our way back up, data will be set, and the original data will be mapped the way it should be for the given URI
this._add(listener, Structr.copy(this.data, true), batch.paths);
if(batch._next)
{
this.add(batch._next);
}
this.last = this._queue[0].target;
},
/**
*/
'init': function()
{
return this;
},
/**
*/
'hasNext': function()
{
return !!this._queue.length;
},
/**
*/
'next': function()
{
if(this._queue.length)
{
var thru = this._queue.pop(),
target = thru.target;
this.current = target;
if(target.paths)
{
var route = this.origin.getRoute({ channel: target });
this._addListeners(route.listeners, route.data, target.paths);
return this.next();
}
//heavier...
/*var route = this.expandRoute(this._queue.length-1);
if(!route) return this.next();
target = route.target,
thru = route.thru;
this._queue.pop();*/
this.current = target;
if(target.isMiddleware && this._used[target.id]) return this.next();
//keep tabs of what's used so there's no overlap. this will happen when we get back to the router
//for middleware specified in path -> to -> route
this._used[target.id] = thru;
this._prepare(target, thru.data, thru.hasParams, thru.paths);
return true;
}
return false;
},
/**
* expands all the routes (middleware even). See leche for example
*/
/*'expandThru': function()
{
var i = 0;
while(i < this._queue.length)
{
if(this.expandRoute(i))
{
i++;
}
else
{
i = 0;
}
}
},*/
/**
*/
/*'expandRoute': function(index)
{
var thru = this._queue[index],
target = thru.target;
if(target.paths)
{
this._queue.splice(index, 1);
var route = this.origin.getRoute({ channel: target }), n = this._queue.length;
this._addListeners(route.listeners, route.data, target.paths);
return false;
}
return { target: target, thru: thru };
},*/
/**
*/
'forward': function(channel, callback)
{
return this.origin.dispatch(Parser.parse(channel), this.data, { inner: this.inner, req: this.req }, callback);
},
/**
*/
'thru': function(channel, ops)
{
var self = this;
if(ops) Structr.copy(ops, this, true);
this._queue.push({ target: Parser.parse('-stream ' + channel).channels[0] });
this.next();
/*this.origin.dispatch(Parser.parse('-stream' + channel), this.data, Structr.copy({ inner: this.inner, req: this.req, _next: callback }, ops, true), function(request)
{
request.pipe(self);
})*/
},
/**
*/
'_addListeners': function(listeners, data, paths)
{
if(listeners instanceof Array)
{
for(var i = listeners.length; i--;)
{
this._add(listeners[i], data, paths);
}
return;
}
},
/**
* adds middleware to the END of the call stack
*/
'add': function(callback)
{
this._queue.unshift(this._func(callback));
},
/**
* adds middleware to the beginning of the call stack
*/
'unshift': function(callback)
{
this._queue.push(this._func(callback));
},
/**
*/
'_func': function(callback)
{
return { target: { callback: callback }, data: {} };
},
/**
*/
'_add': function(route, data, paths)
{
var current = route, _queue = this._queue,
hasParams = false;
if(!data) data = {};
while(current)
{
for(var i = paths.length; i--;)
{
var opath = paths[i],
cpath = route.path[i],
param,
value;
if(cpath.param && !opath.param)
{
param = cpath.name;
value = opath.name;
}
else
if(cpath.param && opath.param)
{
param = cpath.name;
value = this.data[opath.name];
}
else
{
continue;
}
hasParams = true;
this.data[param] = data[param] = value;
// if(!this.data[param]) this.data[param] = value;
}
//make sure not to use the same route twice. this will happen especially with middleware specified as /middleware/*
_queue.push({ target: current, data: data, hasParams: hasParams, paths: paths });
current = current.thru;
}
},
/**
*/
'_prepare': function(target, data, hasParams, paths)
{
//call once, then dispose
if(target.meta && target.meta.one)
{
target.dispose();
}
if(hasParams)
{
Structr.copy(data, this.data,true);
}
if(target.path) this.currentChannel = utils.pathToString(target.path, this.data);
this._callback(target, data);
},
/**
*/
'channelPath': function(index)
{
return utils.pathToString(this._queue[index].target.path || [], this.data);
},
/**
*/
'_callback': function(target, data)
{
return target.callback.call(this, this);
}
});
module.exports = Request;
+282
View File
@@ -0,0 +1,282 @@
var Structr = require('structr'),
Parser = require('./parser'),
utils = require('./utils'),
middleware = require('../middleware/meta'),
Request = require('./request'),
Collection = require('./collection');
/**
* Glorious.
*/
var Router = Structr({
/**
* Constructor. What else do you think it is?
*/
'__construct': function(ops)
{
if(!ops) ops = {};
this.RequestClass = ops.RequestClass || Request;
this._collection = new Collection(ops);
this._allowMultiple = !!ops.multi;
},
/**
* listens to the given expression for any change
*/
'on': function(expr, ops, callback)
{
if(!callback)
{
callback = ops;
ops = null;
}
for(var i = expr.channels.length; i--;)
{
var single = utils.channel(expr, i),
existingRoute = this.getRoute(single);
if(existingRoute.listeners.length && !this._allowMultiple && !this._middleware().allowMultiple(single))
{
var epath = existingRoute.listeners[0].path;
//use-case: server crashes, and reboots. Needs to override current registered path (which is trash)
if(existingRoute.listeners[0].meta.overridable)
{
existingRoute.listeners[0].dispose();
}
else
//if both are params, then there's a collission.
if(single.channel.paths[single.channel.paths.length-1].param == epath[epath.length-1].param)
{
throw new Error('Path "'+utils.pathToString(single.channel.paths)+'" already exists');
}
};
this._middleware().setRoute(channel);
}
return this._collection.add(expr, callback);
},
/**
*/
'_middleware': function()
{
return this.controller.metaMiddleware;
},
/**
*/
'hasRoute': function(channel, data)
{
return !!this.getRoute(channel, data).listeners.length;
},
/**
*/
'hasRoutes': function(expr, data)
{
for(var i = expr.channels.length; i--;)
{
if(this.hasRoute(utils.channel(expr, i), data)) return true;
}
return false;
},
/**
*/
'getRoute': function(single, data)
{
var route = this._collection.route(single.channel);
var r = this._middleware().getRoute({
expr: single,
router: this,
route: route,
data: data,
listeners: this._filterRoute(single, route)
});
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//this chunk is experimental. Moreso to test its usefulness over anything. The initial
//thought is enable the ability for routes with *different* metadata to share the same channel. This is great
//If say, you have web-workers which use the same channels, but different cluster IDs, so a master server knows
//what data to send to what web-worker. This COULD be specified in the URI structure, but that feels a bit messy. We'll see if this works first. It's nice and clean.
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
if(!this._middleware().allowMultiple(route) && !this._allowMultiple && r.listeners.length)
{
r.listeners = [r.listeners[0]];
}
return r;
},
/**
*/
'dispatch': function(expr, data, ops, callback)
{
//only one for now. may change later on.
for(var i = expr.channels.length; i--;)
{
if(this._dispatch(utils.channel(expr, i), data, ops, callback)) return true;
}
return false;
},
/**
*/
'_dispatch': function(expr, data, ops, callback)
{
if(data instanceof Function)
{
callback = data;
data = undefined;
ops = undefined;
}
if(ops instanceof Function)
{
callback = ops;
ops = undefined;
}
if(!ops) ops = {};
if(!data) data = {};
channel = utils.pathToString(expr.channel.paths);
var inf = this.getRoute(expr, data);
//warnings are good incase this shouldn't happen
if(!inf.listeners.length)
{
if(!ops.ignoreWarning && !expr.meta.passive)
{
console.warn('The %s route "%s" does not exist', expr.type, channel);
}
//some callbacks are passive, meaning the dispatched request is *optional* ~ like a plugin
if(expr.meta.passive && callback)
{
callback(null, 'Route Exists');
}
return false;
}
var newOps = {
//router is set to controller because router() is used in loader. keeps things consistent, and using "this.controller.pull" is vague
router: this.controller,
//where the route lives
origin: this,
//data attached, duh.
data: inf.data,
//inner data
inner: ops.inner || {},
channel: channel,
paths: inf.expr.channel.paths,
//the metadata attached to the expression. Tells all about how it should be handled
meta: expr.meta,
//where is the dispatch coming from? Useful for hooks
from: ops.from || this.controller,
//the listeners to dispatch
listeners: inf.listeners,
//the final callback after everything's done ;)
callback: callback
};
Structr.copy(newOps, ops, true);
this._callListeners(ops);
return true;
},
/**
*/
'_callListeners': function(newOps)
{
for(var i = newOps.listeners.length; i--;)
{
Structr.copy(newOps, new this.RequestClass(newOps.listeners[i], newOps), true).init().next();
}
},
/**
* filters routes based on metadata
*/
'_filterRoute': function(expr, route)
{
if(!route) return [ ];
var listeners = (route.listeners || []).concat();
//Useful if there are groups of listeners with the same channel, but should not communicate
//with each other. E.g: two apps with slaves, sending queues to thyme. Thyme would need to know exactly where the slaves are
for(var name in expr.meta)
{
//the value of the metadata to search
var value = expr.meta[name];
//make sure that it's not *just* defined. This is important
//for metadata such as streams
if(value === 1) continue;
//loop through the listeners and start filtering
for(var i = listeners.length; i--;)
{
var listener = listeners[i];
if(listener.meta.unfilterable) break;
var metaV = listener.meta[name];
//if value == 1, then the tag just needs to exist
if(metaV != value && metaV != '*')
{
listeners.splice(i, 1);
}
}
}
return listeners;
}
});
module.exports = Router;
+115
View File
@@ -0,0 +1,115 @@
/**
* replaces params of the given expression
*/
exports.replaceParams = function(expr, params)
{
var path;
for(var i = expr.channel.paths.length; i--;)
{
path = expr.channel.paths[i];
if(path.param)
{
path.param = false;
path.name = params[path.name];
//no name? IT MUST EXIST. DELETE!
if(!path.name) expr.channel.paths.splice(i, 1);
}
}
return expr;
}
exports.channel = function(expr, index)
{
return { type: expr.type, channel: expr.channels[index], meta: expr.meta || {} };
}
exports.pathToString = function(path, data)
{
var paths = [];
if(!data) data = {};
for(var i = 0, n = path.length; i < n; i++)
{
var pt = path[i],
part;
if(pt.param && data[pt.name])
{
part = data[pt.name];
}
else
{
part = pt.param ? ':' + pt.name : pt.name;
}
paths.push(part);
}
return paths.join('/');
}
//old
/*exports.channelToStr = function(channel, omit)
{
var buffer = [];
if(!omit) omit = [];
if(expr.type && !omit.type) buffer.push(expr.type);
if(!omit.meta)
for(var key in channel.meta)
{
buffer.push('-'+key+'='+expr.meta[key]);
}
var current = expr.channel;
var middleware = [];
while(current)
{
var paths = [];
for(var i = 0, n = current.paths.length; i < n; i++)
{
var path = current.paths[i];
paths.push((path.param ? ':' : '')+ path.name);
}
middleware.unshift(paths.join('/'));
current = current.thru;
}
buffer.push(middleware.join(' -> '));
return buffer.join(' ');
}*/
exports.passThrusToArray = function(channel)
{
var cpt = channel.thru,
thru = [];
while(cpt)
{
thru.push(this._pathToString(cpt.paths));
cpt = cpt.thru;
}
return thru;
}
+200
View File
@@ -0,0 +1,200 @@
var Structr = require('structr'),
routeMiddleware = require('./middleware/route'),
metaMiddleware = require('./middleware/meta'),
Parser = require('./concrete/parser'),
Janitor = require('sk/core/garbage').Janitor,
utils = require('./concrete/utils');
var AbstractController = Structr({
/**
*/
'__construct': function(target)
{
this.metaMiddleware = metaMiddleware(this);
this.routeMiddleware = routeMiddleware(this);
this.metaMiddleware.init();
this.routeMiddleware.init();
this._channels = {};
},
/**
*/
'has': function(type, ops)
{
var expr = this._parse(type, ops);
return this._router(expr).hasRoutes(expr);
},
/**
*/
'getRoute': function(type, ops)
{
var expr = this._parse(type, ops);
return this._router(expr).getRoute(utils.channel(expr, 0));
},
/**
*/
'on': function(target)
{
var ja = new Janitor();
for(var type in target)
{
ja.addDisposable(this.on(type, {}, target[type]));
}
return ja;
},
/**
*/
'second on': function(type, callback)
{
return this.on(type, {}, callback);
},
/**
*/
'third on': function(type, ops, callback)
{
var expr = this._parse(type, ops),
router = this.routeMiddleware.router(expr);
for(var i = expr.channels.length; i--;)
{
var pathStr = utils.pathToString(expr.channels[i].paths);
if(!this._channels[pathStr])
{
this.addChannel(pathStr, Structr.copy(utils.channel(expr, i)));
}
}
return router.on(expr, ops, callback);
},
/**
*/
'channels': function()
{
return this._channels;
},
/**
*/
'addChannel': function(path, singleChannel)
{
for(var prop in singleChannel.meta)
{
singleChannel.meta[prop] = '*';
}
this._channels[path] = singleChannel;
},
/**
* flavor picker for operations. In the string, or in the ops ;)
*/
'_parse': function(type, ops)
{
var expr = typeof type != 'object' ? Parser.parse(type) : Structr.copy(type);
if(ops)
{
if(ops.meta) Structr.copy(ops.meta, expr.meta);
if(ops.type) expr.type = ops.type;
}
return expr;
},
/**
*/
'_router': function(expr)
{
return this.routeMiddleware.router(expr);
},
/**
*/
'_createTypeMethod': function(method)
{
var self = this;
var func = this[ method ] = function(channel, data, ops, callback)
{
if(!ops) ops = {};
ops.type = method;
var expr = this._parse(channel, ops);
return self._router(expr).dispatch(expr, data, ops, callback);
}
var router = self._router( { type: method });
Structr.copy(router, func, true);
}
});
var ConcreteController = AbstractController.extend({
/**
*/
'override __construct': function()
{
this._super();
var self = this;
//make channels data-bindable
this.on({
/**
*/
'pull channels': function()
{
return self.channels();
}
});
},
/**
*/
'override addChannel': function(path, singleChannel)
{
this._super(path, singleChannel);
//keep the same format as the channels so the end-point is handled exactly the same
var toPush = {};
toPush[path] = singleChannel;
this.push('channels', toPush, { ignoreWarning: true });
}
})
module.exports = ConcreteController;
+11
View File
@@ -0,0 +1,11 @@
var Loader = require('./loader');
//or a new, sandboxed router
exports.router = function()
{
return new Loader();
}
//singleton to boot
exports.router().copyTo(module.exports, true);
+76
View File
@@ -0,0 +1,76 @@
var Structr = require('structr'),
Controller = require('./controller');
try
{
require.paths.unshift(__dirname + '/beans');
}catch(e)
{
//break the web.
}
var Loader = Controller.extend({
/**
*/
'override __construct': function()
{
this._super();
this._params = {};
},
/**
*/
'params': function(params)
{
if(typeof params == 'string') return this._params[params];
Structr.copy(params || {}, this._params);
return this;
},
/**
*/
'require': function()
{
for(var i = arguments.length; i--;)
{
this._require(arguments[i]);
}
return this;
},
/**
*/
'_require': function(source)
{
if(source instanceof Array)
{
for(var i = source.length; i--;)
{
this._require(source[i]);
}
}
else
if(typeof src == 'object' && typeof src.bean == 'function')
{
source.plugin(this._controller, source.params || this._params[ source.name ] || {});
}
else
{
return false;
}
return true;
}
});
module.exports = Loader;
@@ -0,0 +1,138 @@
var Structr = require('structr');
module.exports = function()
{
var mw = new exports.Middleware();
mw.init = function()
{
//needs to be useable online = manual
mw.add(require('./rotate'));
}
return mw;
}
exports.Middleware = Structr({
/**
*/
'__construct': function()
{
//middleware specific to metadata
this._toMetadata = {};
//middleware which handles everything
this._universal = {};
},
/**
*/
'add': function(module)
{
var self = this;
if(module.all)
{
module.all.forEach(function(type)
{
if(!self._universal[type]) self._universal[type] = [];
self._universal[type].push(module);
});
}
module.meta.forEach(function(name)
{
self._toMetadata[name] = module;
});
},
/**
*/
'getRoute': function(ops)
{
//parse metadata TO, and FROM
var mw = this._getMW(ops.route ? ops.route.meta : {}, 'getRoute').concat(this._getMW(ops.expr.meta));
return this._eachMW(ops, mw, function(cur, ops)
{
return cur.getRoute(ops);
});
},
/**
*/
'setRoute': function(ops)
{
var mw = this._getMW(ops.meta, 'setRoute');
return this._eachMW(ops, mw, function(cur, ops)
{
return cur.setRoute(ops);
});
},
/**
*/
'allowMultiple': function(expr)
{
var mw = this._getMW(expr.meta);
for(var i = mw.length; i--;)
{
if(mw[i].allowMultiple) return true;
}
return false;
},
/**
*/
'_getMW': function(meta, uni)
{
var mw = (this._universal[uni] || []).concat();
for(var name in meta)
{
if(meta[name] == undefined) continue;
var handler = this._toMetadata[name];
if(handler && mw.indexOf(handler) == -1) mw.push(handler);
}
return mw;
},
/**
*/
'_eachMW': function(ops, mw, each)
{
var cops = ops,
newOps;
for(var i = mw.length; i--;)
{
if(newOps = each(mw[i], cops))
{
cops = newOps;
}
}
return cops;
}
});
@@ -0,0 +1,39 @@
exports.rotator = function(target, meta)
{
if(!target) target = {};
target.meta = [meta];
target.allowMultiple = true;
target.getRoute = function(ops)
{
var route = ops.route,
listeners = ops.listeners;
//if rotate is specified, then we need to rotate it (round-robin). There's a catch though...
//because the above *might* have filtered down to metadata values, we need to only rotate what's left, AND
//the rotate index must be stuck with the routes rotate metadata.
//Also, if the router can have multiple, then we cannot do round-robin. FUcKs ShiT Up.
if(!ops.router._allowMultiple && route && route.meta && route.meta[meta] != undefined && listeners.length)
{
route.meta[meta] = (++route.meta[meta]) % listeners.length;
//only ONE listener now..
ops.listeners = [listeners[route.meta[meta]]];
}
}
target.setRoute = function(ops)
{
}
}
exports.rotator(exports, 'rotate');
@@ -0,0 +1,8 @@
exports.meta = ['store'];
exports.getRoute = function(){};
exports.setRoute = function(ops)
{
};
@@ -0,0 +1,13 @@
var Router = require('../../../concrete/router');
exports.types = ['dispatch'];
exports.test = function(expr)
{
return !expr.type || expr.type == 'dispatch' ? 'dispatch' : null;
}
exports.newRouter = function()
{
return new Router({multi:true});
}
@@ -0,0 +1,91 @@
var Structr = require('structr');
module.exports = function(controller)
{
var mw = new exports.Middleware(controller);
mw.init = function()
{
//needs to be useable online = manual
mw.add(require('./pushPull/push'));
mw.add(require('./pushPull/pull'));
mw.add(require('./default'));
}
return mw;
}
exports.Middleware = Structr({
/**
*/
'__construct': function(controller)
{
this._middleware = [];
this._controller = controller;
//instantiated routers
this._routers = {};
//types of routers
this.types = [];
},
/**
*/
'add': function(module)
{
this._middleware.push(module);
this.types = module.types.concat(this.types);
for(var i = module.types.length; i--;)
{
this._controller._createTypeMethod(module.types[i]);
}
},
/**
*/
'router': function(expr)
{
for(var i = this._middleware.length; i--;)
{
//get the factory name. some middleware may return different routers depending on the expression metadata, such as pull -multi
var mw = this._middleware[i], name = mw.test(expr);
if(name) return this._router(mw, name);
}
return null;
},
/**
*/
'_router': function(tester, name)
{
return this._routers[ name ] || this._newRouter(tester, name);
},
/**
*/
'_newRouter': function(tester, name)
{
var router = tester.newRouter(name);
router.type = name;
router.controller = this._controller;
this._routers[ name ] = router;
return router;
}
});
@@ -0,0 +1,19 @@
var Router = require('../../../../concrete/router'),
Request = require('./request');
exports.types = ['pull','pullMulti'];
exports.test = function(expr)
{
if(expr.type == 'pullMulti') return 'pullMulti';
return expr.type == 'pull' ? (expr.meta && expr.meta.multi ? 'pullMulti' : 'pull') : null;
}
exports.newRouter = function(type)
{
var ops = { RequestClass: Request };
if(type == 'pullMulti') ops.multi = true;
return new Router(ops);
}
@@ -0,0 +1,45 @@
var Request = require('../request'),
Structr = require('structr');
var PullRequest = Request.extend({
/**
*/
'override init': function()
{
this._super();
this._listen(this.callback, this.meta);
return this;
},
/**
*/
'override _callback': function()
{
var ret = this._super.apply(this, arguments);
if(ret != undefined)
{
if(ret == true)
{
}
if(ret.send)
{
ret.send(this);
}
else
{
this.end(ret);
}
}
}
});
module.exports = PullRequest;
@@ -0,0 +1,14 @@
var Router = require('./router'),
PushRequest = require('./request');
exports.types = ['push'];
exports.test = function(expr)
{
return expr.type == 'push' ? 'push' : null;
}
exports.newRouter = function()
{
return new Router({ multi: true, RequestClass: PushRequest });
}
@@ -0,0 +1,30 @@
var Stream = require('../stream'),
PushPullRequest = require('../request');
var PushRequest = PushPullRequest.extend({
/**
*/
'override init': function()
{
this._super();
this.cache();
this.stream.pipe(this);
return this;
},
/**
*/
'override _callback': function(route, data)
{
this._listen(route.callback, route.meta || {});
}
});
module.exports = PushRequest;
@@ -0,0 +1,63 @@
var Router = require('../../../../concrete/router'),
Stream = require('../stream'),
utils = require('../../../../concrete/utils'),
Structr = require('structr');
var PushRouter = Router.extend({
/**
*/
'override on': function(expr, ops, callback)
{
if(!callback)
{
callback = ops;
ops = {};
}
var ret = this._super(expr, ops, callback);
if(expr.meta.pull)
{
this.controller.pull(expr, Structr.copy(ops.data), { ignoreWarning: true }, callback);
}
return ret;
},
/**
*/
'override _callListeners': function(ops)
{
//the stream for pushing content. must be cached incase there's latency.
var stream = new Stream(true),
//no callback? then data's just being pushed, which is okay
callback = ops.callback || function(stream)
{
return ops.data;
}
//of there's a callback, it can return a value which is pushed to the stream
var ret = callback(stream);
//IF there's a value, then we're done
if(ret != undefined)
{
stream.end(ret);
}
//make the stream visible to all listeners
ops.stream = stream;
//SHIBLAM. call the listeners ;)
this._super.apply(this, arguments);
}
});
module.exports = PushRouter;
@@ -0,0 +1,88 @@
var Request = require('../../../concrete/request'),
Stream = require('./stream'),
Structr = require('structr');
/**
*/
var PushPullRequest = Request.extend(Structr.copy(Stream.proto, {
/**
*/
'init': function()
{
this._init();
return this;
},
/**
*/
'_listen': function(listener, meta)
{
//because the framework needs to be easy to use, streams are turned off by default. This
//would be a huge pain in the pass if every time they're required, but they're SUPER important
//if we're trying to stream a large amount of data. What about HTTP? So if it's false, we need to
//add a stream handler.
if(!meta.stream)
{
//the buffer for the streams
var buffer = [], self = this;
function end(err)
{
if(err) return;
//again, it would be a pain in the ass if everytimg we have to do: var value = response[0]. So
//a "batch" must be specified if we're expecting an array, because 99% of the time for in-app route handling,
//only ONE value will be returned.
if(meta.batch)
{
listener.call(self, buffer, err, self);
}
else
{
if(!buffer.length)
{
listener();
}
else
//so again, by default callback the listener as many times as there are batch values
for(var i = 0, n = buffer.length; i < n; i++)
{
listener.call(self, buffer[i], err, self);
}
}
}
this.pipe({
//on write, throw the data into the buffer
write: function(data)
{
buffer.push(data);
},
error: end,
//on end, callback the listener
end: end
});
}
//is the listener expecting a stream? Okay, then pass on the writer to the listener. Only use this for files, http requests, and the
//likes plz, omg you're code would look like shit otherwise >.>
else
{
//more flavor picking. Use this, or the passed obj
listener.call(this, this);
}
}
}));
module.exports = PushPullRequest;
@@ -0,0 +1,209 @@
var Structr = require('structr'),
EventEmitter = require('sk/core/events').EventEmitter;
/**
the bridge between the listener, and responder. Yeah, Yeah. the listener
94 */
var proto = {
/**
*/
'_init': function(ttl)
{
this._em = new EventEmitter();
this.response = {};
if(ttl)
{
this.cache(ttl);
}
},
/**
*/
'cache': function(ttl)
{
if(this._caching) return;
this._caching = true;
//store the buffer incase data comes a little quicker than we can handle it.
var buffer = this._buffer = [], self = this;
this.on({
write: function(chunk)
{
buffer.push(chunk)
}
});
},
/**
*/
'on': function(listeners)
{
for(var type in listeners)
{
this._em.addListener(type, listeners[type]);
}
},
/**
*/
'second on': function(type, callback)
{
this._em.addListener(type, callback);
},
/**
*/
'respond': function(data)
{
this.responded = true;
Structr.copy(data, this.response, true);
return this;
},
/**
*/
'error': function(data)
{
if(!data) return this._error;
this._error = data;
this._em.emit('error', data);
return this;
},
/**
*/
'_sendResponse': function()
{
if(!this._sentResponse)
{
this._sentResponse = true;
//WHOOAAHH, what are you doing!? Okay, I know it looks stupid, it is. BUT, consider this scenario:
//via HTTP, the session object is *saved* to the database once toJSON is called, which is ONLY called when *this* happens. We do not want
//the session to save before we're done writing to it, or handling it.
this.response = JSON.parse(JSON.stringify(this.response));
this._em.emit('response', this.response);
return true;
}
return false;
},
/**
*/
'write': function(data)
{
this._sendResponse();
this._em.emit('write', data);
return this;
},
/**
*/
'end': function(data)
{
//SUPER NOTE: end can be called *once*. After that, all the listeners are disposed of
if(data) this.write(data);
this._sendResponse();
this.finished = true;
this._em.emit('end', data);
//remove the event listeners to avoid mem leaks
this._em.dispose();
return this;
},
/**
*/
'pipe': function(stream)
{
//IF there is a buffer, that means it came faster than we can handle it. This sort of thing
//occurrs when there are pass-thru routes which hold up the final callback. e.g: authenticating a user against
//a database
// if(stream.response) stream.response = this.response;
if(stream.respond && this.responded) stream.respond(this.response);
if(this._buffer && this._buffer.length)
{
for(var i = 0, n = this._buffer.length; i < n; i++)
{
stream.write(this._buffer[i]);
}
}
//already finished? return
if(this.finished)
{
return stream.end();
}
//looks like the bridge is still handling data, so listen for the rest
this.on({
write: function(data)
{
if(stream.write) stream.write(data);
},
end: function()
{
if(stream.end) stream.end();
},
error: function(e)
{
if(stream.error) stream.error(e);
},
response: function(data)
{
if(stream.respond) stream.respond(data);
}
});
}
};
var Stream = Structr(Structr.copy(proto, {
/**
* @param the current request
* @param ttl time to keep cached version in memory before dumping it
*/
'__construct': function(ttl)
{
this._init(ttl);
}
}));
Stream.proto = proto;
module.exports = Stream;
@@ -0,0 +1,26 @@
var exec = require('child_process').exec;
exports.plugin = function(router)
{
router.on({
/**
*/
'push init': function()
{
exec('hostname', function(err, hostname)
{
router.on('pull hostname', function()
{
return hostname;
});
router.push('hostname', hostname.replace('\n',''));
});
},
})
}
+21
View File
@@ -0,0 +1,21 @@
require('sk/node/log');
//need this for global beanpole ~ not cached with NPM.
if(global.beanpole)
{
module.exports = global.beanpole;
}
else
{
var Loader = require('./loader');
exports.router = function()
{
return new Loader();
}
exports.router().copyTo(module.exports, true);
global.beanpole = module.exports;
}
+171
View File
@@ -0,0 +1,171 @@
var Structr = require('structr'),
Loader = require('../core/loader'),
fs = require('fs'),
pt = require('path');
//let coffeescript inject require hooks
require('coffee-script');
function unableToLoad(bean)
{
console.error('Unable to load bean "%s"', bean);
}
//replaces relative paths, with abs paths for beans. Primarily used for the package.json
//config
function replaceRelWithAbsPath(config, cwd)
{
for(var property in config)
{
var value = config[property];
if(typeof value == 'string' && value.substr(0,2) == './')
{
config[property] = fs.realpathSync(cwd+value.substr(1));
}
else
if(value instanceof Object)
{
replaceRelWithAbsPath(value, cwd);
}
}
return config;
}
//quick, temporary fix for node > 5.x. breaks
try
{
require.paths.unshift(__dirname + '/beans');
}
catch(e)
{
}
var NodeLoader = Loader.extend({
/**
*/
'override __construct': function()
{
this._super();
this._loaded = [];
},
/**
*/
'override _require': function(source)
{
if(!this._super(source))
{
if(typeof source == 'object')
{
for(var bean in source)
{
this._require2(bean).plugin(this, source[bean]);
}
}
else
if(typeof source == 'string')
{
var bean, self = this, basename = pt.basename(source);
if(basename == 'package.json')
{
var pkg = JSON.parse(fs.readFileSync(source, 'utf8'));
for(var bean in pkg.beans)
{
var params = pkg.beans[bean],
plugin = self._require2(bean);
if(!plugin)
{
unableToLoad(bean);
continue;
}
plugin.plugin(self, replaceRelWithAbsPath(typeof params != 'boolean' ? params : self._params[bean] || {}, pt.dirname(source)));
}
}
else
if(!(bean = this._require2(source)))
{
try
{
//NOT a bean, but a directory for the beans.
fs.readdirSync(source).forEach(function(name)
{
//hidden file
if(name.substr(0,1) == '.') return;
self._require2(source + '/' + name).plugin(self, self._params[name] || {});
});
}
catch(e)
{
console.log(e.stack)
console.warn('Unable to load beans from directory %s', source);
unableToLoad(source);
}
}
else
{
bean.plugin(this, self._params[source.split('/').pop()] || {});
}
}
else
{
return false;
}
return this;
}
//this gets hit if old require is true
return this;
},
/**
*/
'_require2': function(bean)
{
try
{
var path = require.resolve(bean);
}
catch(e)
{
return false;
}
var ret = require(bean),
name = pt.dirname(path).split('/').pop();
if(this._loaded.indexOf(path) > -1)
{
console.notice('Cannot reload bean "%s"', bean);
return { plugin: function() {} };
}
this._loaded.push(path);
return ret;
}
});
module.exports = NodeLoader;
+1
View File
@@ -0,0 +1 @@
module.exports = require('../core')
+25
View File
@@ -0,0 +1,25 @@
{
"name": "beanpole",
"description": "Routing on Steroids",
"version": "0.1.16",
"author": "Craig Condon",
"repository": {
"type": "git",
"url": "http://github.com/crcn/beanpole.git"
},
"directories" : { "lib" : "./lib" },
"dependencies": {
"sk":"*",
"vine":"*",
"mime":"*",
"gumbo":"*",
"cashew":"*",
"structr":"*",
"coffee-script":"*"
},
"devDependencies": {
"ebnf-diagram":"*"
},
"main": "./lib/node/index.js"
}
+23
View File
@@ -0,0 +1,23 @@
{
"folders":
[
{
"path": "."
}
],
"build_systems":
[
{
"name":"cbd make",
"cmd":["cbd","make","beanpole"]
},
{
"name":"cbd start",
"cmd":["cbd","start","beanpole"]
},
{
"name":"cbd make+start",
"cmd":["cbd","make+start","beanpole"]
}
]
}
+168
View File
@@ -0,0 +1,168 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>currentDocument</key>
<string>lib/core/concrete/router.js</string>
<key>documents</key>
<array>
<dict>
<key>expanded</key>
<true/>
<key>name</key>
<string>project</string>
<key>regexFolderFilter</key>
<string>!.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
<key>sourceDirectory</key>
<string></string>
</dict>
</array>
<key>fileHierarchyDrawerWidth</key>
<integer>200</integer>
<key>metaData</key>
<dict>
<key>MIT-LICENSE.txt</key>
<dict>
<key>caret</key>
<dict>
<key>column</key>
<integer>61</integer>
<key>line</key>
<integer>10</integer>
</dict>
<key>firstVisibleColumn</key>
<integer>0</integer>
<key>firstVisibleLine</key>
<integer>0</integer>
</dict>
<key>docs/beans/README.md</key>
<dict>
<key>caret</key>
<dict>
<key>column</key>
<integer>8</integer>
<key>line</key>
<integer>43</integer>
</dict>
<key>columnSelection</key>
<false/>
<key>firstVisibleColumn</key>
<integer>0</integer>
<key>firstVisibleLine</key>
<integer>0</integer>
<key>selectFrom</key>
<dict>
<key>column</key>
<integer>2</integer>
<key>line</key>
<integer>43</integer>
</dict>
<key>selectTo</key>
<dict>
<key>column</key>
<integer>8</integer>
<key>line</key>
<integer>43</integer>
</dict>
</dict>
<key>examples/ws/client/release/index.js</key>
<dict>
<key>caret</key>
<dict>
<key>column</key>
<integer>0</integer>
<key>line</key>
<integer>1232</integer>
</dict>
<key>firstVisibleColumn</key>
<integer>0</integer>
<key>firstVisibleLine</key>
<integer>1216</integer>
</dict>
<key>lib/core/concrete/router.js</key>
<dict>
<key>caret</key>
<dict>
<key>column</key>
<integer>7</integer>
<key>line</key>
<integer>266</integer>
</dict>
<key>firstVisibleColumn</key>
<integer>0</integer>
<key>firstVisibleLine</key>
<integer>234</integer>
</dict>
<key>lib/core/controller.js</key>
<dict>
<key>caret</key>
<dict>
<key>column</key>
<integer>4</integer>
<key>line</key>
<integer>9</integer>
</dict>
<key>firstVisibleColumn</key>
<integer>0</integer>
<key>firstVisibleLine</key>
<integer>0</integer>
</dict>
<key>lib/core/index.js</key>
<dict>
<key>caret</key>
<dict>
<key>column</key>
<integer>0</integer>
<key>line</key>
<integer>0</integer>
</dict>
<key>firstVisibleColumn</key>
<integer>0</integer>
<key>firstVisibleLine</key>
<integer>0</integer>
</dict>
<key>lib/core/loader.js</key>
<dict>
<key>caret</key>
<dict>
<key>column</key>
<integer>1</integer>
<key>line</key>
<integer>7</integer>
</dict>
<key>firstVisibleColumn</key>
<integer>0</integer>
<key>firstVisibleLine</key>
<integer>0</integer>
</dict>
<key>lib/core/middleware/route/pushPull/push/router.js</key>
<dict>
<key>caret</key>
<dict>
<key>column</key>
<integer>4</integer>
<key>line</key>
<integer>9</integer>
</dict>
<key>firstVisibleColumn</key>
<integer>0</integer>
<key>firstVisibleLine</key>
<integer>17</integer>
</dict>
</dict>
<key>openDocuments</key>
<array>
<string>MIT-LICENSE.txt</string>
<string>docs/beans/README.md</string>
<string>examples/ws/client/release/index.js</string>
<string>lib/core/controller.js</string>
<string>lib/core/loader.js</string>
<string>lib/core/concrete/router.js</string>
<string>lib/core/index.js</string>
</array>
<key>showFileHierarchyDrawer</key>
<true/>
<key>windowFrame</key>
<string>{{210, 4}, {1230, 874}}</string>
</dict>
</plist>
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2010 Alexis Sellier (cloudhead) , Marak Squires
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.
+30
View File
@@ -0,0 +1,30 @@
<h1>colors.js - get color and style in your node.js console like what</h1>
<img src="http://i.imgur.com/goJdO.png" border = "0"/>
var sys = require('sys');
var colors = require('./colors');
sys.puts('hello'.green); // outputs green text
sys.puts('i like cake and pies'.underline.red) // outputs red underlined text
sys.puts('inverse the color'.inverse); // inverses the color
sys.puts('OMG Rainbows!'.rainbow); // rainbow (ignores spaces)
<h2>colors and styles!</h2>
- bold
- italic
- underline
- inverse
- yellow
- cyan
- white
- magenta
- green
- red
- grey
- blue
### Authors
#### Alexis Sellier (cloudhead) , Marak Squires , Justin Campbell, Dustin Diaz (@ded)
+230
View File
@@ -0,0 +1,230 @@
/*
colors.js
Copyright (c) 2010 Alexis Sellier (cloudhead) , Marak Squires
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.
*/
exports.mode = "console";
// prototypes the string object to have additional method calls that add terminal colors
var addProperty = function (color, func) {
exports[color] = function(str) {
return func.apply(str);
};
String.prototype.__defineGetter__(color, func);
}
var isHeadless = (typeof module !== 'undefined');
['bold', 'underline', 'italic', 'inverse', 'grey', 'black', 'yellow', 'red', 'green', 'blue', 'white', 'cyan', 'magenta'].forEach(function (style) {
// __defineGetter__ at the least works in more browsers
// http://robertnyman.com/javascript/javascript-getters-setters.html
// Object.defineProperty only works in Chrome
addProperty(style, function () {
return isHeadless ?
stylize(this, style) : // for those running in node (headless environments)
this.replace(/( )/, '$1'); // and for those running in browsers:
// re: ^ you'd think 'return this' works (but doesn't) so replace coerces the string to be a real string
});
});
// prototypes string with method "rainbow"
// rainbow will apply a the color spectrum to a string, changing colors every letter
addProperty('rainbow', function () {
if (!isHeadless) {
return this.replace(/( )/, '$1');
}
var rainbowcolors = ['red','yellow','green','blue','magenta']; //RoY G BiV
var exploded = this.split("");
var i=0;
exploded = exploded.map(function(letter) {
if (letter==" ") {
return letter;
}
else {
return stylize(letter,rainbowcolors[i++ % rainbowcolors.length]);
}
});
return exploded.join("");
});
function stylize(str, style) {
if (exports.mode == 'console') {
var styles = {
//styles
'bold' : ['\033[1m', '\033[22m'],
'italic' : ['\033[3m', '\033[23m'],
'underline' : ['\033[4m', '\033[24m'],
'inverse' : ['\033[7m', '\033[27m'],
//grayscale
'white' : ['\033[37m', '\033[39m'],
'grey' : ['\033[90m', '\033[39m'],
'black' : ['\033[30m', '\033[39m'],
//colors
'blue' : ['\033[34m', '\033[39m'],
'cyan' : ['\033[36m', '\033[39m'],
'green' : ['\033[32m', '\033[39m'],
'magenta' : ['\033[35m', '\033[39m'],
'red' : ['\033[31m', '\033[39m'],
'yellow' : ['\033[33m', '\033[39m']
};
} else if (exports.mode == 'browser') {
var styles = {
//styles
'bold' : ['<b>', '</b>'],
'italic' : ['<i>', '</i>'],
'underline' : ['<u>', '</u>'],
'inverse' : ['<span style="background-color:black;color:white;">', '</span>'],
//grayscale
'white' : ['<span style="color:white;">', '</span>'],
'grey' : ['<span style="color:grey;">', '</span>'],
'black' : ['<span style="color:black;">', '</span>'],
//colors
'blue' : ['<span style="color:blue;">', '</span>'],
'cyan' : ['<span style="color:cyan;">', '</span>'],
'green' : ['<span style="color:green;">', '</span>'],
'magenta' : ['<span style="color:magenta;">', '</span>'],
'red' : ['<span style="color:red;">', '</span>'],
'yellow' : ['<span style="color:yellow;">', '</span>']
};
} else if (exports.mode == 'none') {
return str;
} else {
console.log('unsupported mode, try "browser", "console" or "none"');
}
return styles[style][0] + str + styles[style][1];
};
// don't summon zalgo
addProperty('zalgo', function () {
return zalgo(this);
});
// please no
function zalgo(text, options) {
var soul = {
"up" : [
'̍','̎','̄','̅',
'̿','̑','̆','̐',
'͒','͗','͑','̇',
'̈','̊','͂','̓',
'̈','͊','͋','͌',
'̃','̂','̌','͐',
'̀','́','̋','̏',
'̒','̓','̔','̽',
'̉','ͣ','ͤ','ͥ',
'ͦ','ͧ','ͨ','ͩ',
'ͪ','ͫ','ͬ','ͭ',
'ͮ','ͯ','̾','͛',
'͆','̚'
],
"down" : [
'̖','̗','̘','̙',
'̜','̝','̞','̟',
'̠','̤','̥','̦',
'̩','̪','̫','̬',
'̭','̮','̯','̰',
'̱','̲','̳','̹',
'̺','̻','̼','ͅ',
'͇','͈','͉','͍',
'͎','͓','͔','͕',
'͖','͙','͚','̣'
],
"mid" : [
'̕','̛','̀','́',
'͘','̡','̢','̧',
'̨','̴','̵','̶',
'͜','͝','͞',
'͟','͠','͢','̸',
'̷','͡',' ҉'
]
},
all = [].concat(soul.up, soul.down, soul.mid),
zalgo = {};
function randomNumber(range) {
r = Math.floor(Math.random()*range);
return r;
};
function is_char(character) {
var bool = false;
all.filter(function(i){
bool = (i == character);
});
return bool;
}
function heComes(text, options){
result = '';
options = options || {};
options["up"] = options["up"] || true;
options["mid"] = options["mid"] || true;
options["down"] = options["down"] || true;
options["size"] = options["size"] || "maxi";
var counts;
text = text.split('');
for(var l in text){
if(is_char(l)) { continue; }
result = result + text[l];
counts = {"up" : 0, "down" : 0, "mid" : 0};
switch(options.size) {
case 'mini':
counts.up = randomNumber(8);
counts.min= randomNumber(2);
counts.down = randomNumber(8);
break;
case 'maxi':
counts.up = randomNumber(16) + 3;
counts.min = randomNumber(4) + 1;
counts.down = randomNumber(64) + 3;
break;
default:
counts.up = randomNumber(8) + 1;
counts.mid = randomNumber(6) / 2;
counts.down= randomNumber(8) + 1;
break;
}
var arr = ["up", "mid", "down"];
for(var d in arr){
var index = arr[d];
for (var i = 0 ; i <= counts[index]; i++)
{
if(options[index]) {
result = result + soul[index][randomNumber(soul[index].length)];
}
}
}
}
return result;
};
return heComes(text);
}
addProperty('stripColors', function() {
return ("" + this).replace(/\u001b\[\d+m/g,'');
});
+20
View File
@@ -0,0 +1,20 @@
<!DOCTYPE HTML>
<html lang="en-us">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Colors Example</title>
<script src="colors.js"></script>
<script type="text/javascript">
console.log('Rainbows are fun!'.rainbow);
console.log('So '.italic + 'are'.underline + ' styles! '.bold + 'inverse'.inverse);
console.log('Chains are also cool.'.bold.italic.underline.red);
</script>
</head>
<body>
<script>
document.write('Rainbows are fun!'.rainbow + '<br>');
document.write('So '.italic + 'are'.underline + ' styles! '.bold + 'inverse'.inverse + '<br>');
document.write('Chains are also cool.'.bold.italic.underline.red);
</script>
</body>
</html>
+20
View File
@@ -0,0 +1,20 @@
var util = require('util');
var colors = require('./colors');
//colors.mode = "browser";
var test = colors.red("hopefully colorless output");
util.puts('Rainbows are fun!'.rainbow);
util.puts('So '.italic + 'are'.underline + ' styles! '.bold + 'inverse'.inverse); // styles not widely supported
util.puts('Chains are also cool.'.bold.italic.underline.red); // styles not widely supported
//util.puts('zalgo time!'.zalgo);
util.puts(test.stripColors);
util.puts("a".grey + " b".black);
util.puts(colors.rainbow('Rainbows are fun!'));
util.puts(colors.italic('So ') + colors.underline('are') + colors.bold(' styles! ') + colors.inverse('inverse')); // styles not widely supported
util.puts(colors.bold(colors.italic(colors.underline(colors.red('Chains are also cool.'))))); // styles not widely supported
//util.puts(colors.zalgo('zalgo time!'));
util.puts(colors.stripColors(test));
util.puts(colors.grey("a") + colors.black(" b"));
+14
View File
@@ -0,0 +1,14 @@
{
"name": "colors",
"description": "get colors in your node.js console like what",
"version": "0.5.1",
"author": "Marak Squires",
"repository": {
"type": "git",
"url": "http://github.com/Marak/colors.js.git"
},
"engines": {
"node": ">=0.1.90"
},
"main": "colors"
}
+11
View File
@@ -0,0 +1,11 @@
{
"name": "structr",
"description": "Clean OO structure for Javascript.",
"version": "0.0.10",
"author": "Craig Condon",
"repository": {
"type": "git",
"url": "http://github.com/spiceapps/Structr.git"
},
"main": "./structr.js"
}
+488
View File
@@ -0,0 +1,488 @@
var Structr = function (target, parent)
{
if (!parent) parent = Structr.fh({});
var that = Structr.extend.apply(null, [parent].concat(target))
that.__construct.prototype = that;
if(!that.__construct.extend)
//allow for easy extending.
that.__construct.extend = function()
{
return Structr(Structr.argsToArray(arguments), that);
};
//return the constructor
return that.__construct;
};
Structr.argsToArray = function(args)
{
var ar = new Array(args.length);
for(var i = args.length; i--;) ar[i] = args[i];
return ar;
}
Structr.copy = function (from, to, lite)
{
if(typeof to == 'boolean')
{
lite = to;
to = undefined;
}
if (!to) to = from instanceof Array ? [] : {};
var i;
for(i in from)
{
var fromValue = from[i],
toValue = to[i],
newValue;
//don't copy anything fancy other than objects and arrays. this could really screw classes up, such as dates.... (yuck)
if (!lite && typeof fromValue == 'object' && (!fromValue || fromValue.__proto__ == Object.prototype || fromValue.__proto__ == Array.prototype))
{
//if the toValue exists, and the fromValue is the same data type as the TO value, then
//merge the FROM value with the TO value, instead of replacing it
if (toValue && fromValue instanceof toValue.constructor)
{
newValue = toValue;
}
//otherwise replace it, because FROM has priority over TO
else
{
newValue = fromValue instanceof Array ? [] : {};
}
Structr.copy(fromValue, newValue);
}
else
{
newValue = fromValue;
}
to[i] = newValue;
}
return to;
};
//returns a method owned by an object
Structr.getMethod = function (that, property)
{
return function()
{
return that[property].apply(that, arguments);
};
};
Structr.wrap = function(that, prop)
{
if(that._wrapped) return that;
that._wrapped = true;
function wrap(target)
{
return function()
{
return target.apply(that, arguments);
}
}
if(prop)
{
that[prop] = wrap(target[prop]);
return that;
}
for(var property in that)
{
var target = that[property];
if(typeof target == 'function')
{
that[property] = wrap(target);
}
}
return that;
}
//finds all properties with modifiers
Structr.findProperties = function (target, modifier)
{
var props = [],
property;
for(property in target)
{
var v = target[property];
if (v && v[modifier])
{
props.push(property);
}
}
return props;
};
Structr.nArgs = function(func)
{
var inf = func.toString().replace(/\{[\W\S]+\}/g, '').match(/\w+(?=[,\)])/g);
return inf ? inf.length :0;
}
Structr.getFuncsByNArgs = function(that, property)
{
return that.__private['overload::' + property] || (that.__private['overload::' + property] = {});
}
Structr.getOverloadedMethod = function(that, property, nArgs)
{
var funcsByNArgs = Structr.getFuncsByNArgs(that, property);
return funcsByNArgs[nArgs];
}
Structr.setOverloadedMethod = function(that, property, func, nArgs)
{
var funcsByNArgs = Structr.getFuncsByNArgs(that, property);
if(func.overloaded) return funcsByNArgs;
funcsByNArgs[nArgs || Structr.nArgs(func)] = func;
return funcsByNArgs;
}
//modifies how properties behave in a class
Structr.modifiers = {
/**
* overrides given method
*/
m_override: function (that, property, newMethod)
{
var oldMethod = (that.__private && that.__private[property]) || that[property] || function (){},
parentMethod = oldMethod;
if(oldMethod.overloaded)
{
var overloadedMethod = oldMethod,
nArgs = Structr.nArgs(newMethod);
parentMethod = Structr.getOverloadedMethod(that, property, nArgs);
}
//wrap the method so we can access the parent overloaded function
var wrappedMethod = function ()
{
this._super = parentMethod;
var ret = newMethod.apply(this, arguments);
delete this._super;
return ret;
}
if(oldMethod.overloaded)
{
return Structr.modifiers.m_overload(that, property, wrappedMethod, nArgs);
}
return wrappedMethod;
},
/**
* getter / setter which are physical functions: e.g: test.myName(), and test.myName('craig')
*/
m_explicit: function (that, property, gs)
{
var pprop = '__'+property;
//if GS is not defined, then set defaults.
if (typeof gs != 'object')
{
gs = {};
}
if (!gs.get)
gs.get = function ()
{
return this._value;
}
if (!gs.set)
gs.set = function (value)
{
this._value = value;
}
return function (value)
{
//getter
if (!arguments.length)
{
this._value = this[pprop];
var ret = gs.get.apply(this);
delete this._value;
return ret;
}
//setter
else
{
//don't call the gs if the value isn't the same
if (this[pprop] == value )
return;
//set the current value to the setter value
this._value = this[pprop];
//set
gs.set.apply(this, [value]);
//set the new value. this only matters if the setter set it
this[pprop] = this._value;
}
};
},
/**
*/
m_implicit: function (that, property, egs)
{
//keep the original function available so we can override it
that.__private[property] = egs;
that.__defineGetter__(property, egs);
that.__defineSetter__(property, egs);
},
/**
*/
m_overload: function (that, property, value, nArgs)
{
var funcsByNArgs = Structr.setOverloadedMethod(that, property, value, nArgs);
var multiFunc = function()
{
var func = funcsByNArgs[arguments.length];
if(func)
{
return funcsByNArgs[arguments.length].apply(this, arguments);
}
else
{
var expected = [];
for(var sizes in funcsByNArgs)
{
expected.push(sizes);
}
throw new Error('Expected '+expected.join(',')+' parameters, got '+arguments.length+'.');
}
}
multiFunc.overloaded = true;
return multiFunc;
}
}
//extends from one class to another. note: the TO object should be the parent. a copy is returned.
Structr.extend = function ()
{
var from = arguments[0],
to = {};
for(var i = 1, n = arguments.length; i < n; i++)
{
var obj = arguments[i];
Structr.copy(obj instanceof Function ? obj() : obj, to);
}
var that = {
__private: {
//contains modifiers for all properties of object
propertyModifiers: {}
}
};
Structr.copy(from, that);
var usedProperties = {},
property;
for(property in to)
{
var value = to[property];
var propModifiersAr = property.split(' '), //property is at the end of the modifiers. e.g: override bindable testProperty
propertyName = propModifiersAr.pop(),
modifierList = that.__private.propertyModifiers[propertyName] || (that.__private.propertyModifiers[propertyName] = []);
if (propModifiersAr.length)
{
var propModifiers = {};
for(var i = propModifiersAr.length; i--;)
{
var modifier = propModifiersAr[i];
propModifiers['m_' + propModifiersAr[i]] = 1;
if (modifierList.indexOf(modifier) == -1)
{
modifierList.push(modifier);
}
}
if(propModifiers.m_merge)
{
value = Structr.copy(from[propertyName], value);
}
//if explicit, or implicit modifiers are set, then we need an explicit modifier first
if (propModifiers.m_explicit || propModifiers.m_implicit)
{
value = Structr.modifiers.m_explicit(that, propertyName, value);
}
if (propModifiers.m_override)
{
value = Structr.modifiers.m_override(that, propertyName, value);
}
if (propModifiers.m_implicit)
{
//getter is set, don't continue.
Structr.modifiers.m_implicit(that, propertyName, value);
continue;
}
}
for(var j = modifierList.length; j--;)
{
value[modifierList[j]] = true;
}
if(usedProperties[propertyName])
{
var oldValue = that[propertyName];
//first property will NOT be overloaded, so we need to check it here
if(!oldValue.overloaded) Structr.modifiers.m_overload(that, propertyName, oldValue, undefined);
value = Structr.modifiers.m_overload(that, propertyName, value, undefined);
}
usedProperties[propertyName] = 1;
that.__private[propertyName] = that[propertyName] = value;
}
//if the parent constructor exists, and the child constructor IS the parent constructor, it means
//the PARENT constructor was defined, and the CHILD constructor wasn't, so the parent prop was copied over. We need to create a new function, and
//call the parent constructor when the child is instantiated, otherwise it'll be the same class essentially (setting proto)
if (that.__construct && from.__construct && that.__construct == from.__construct)
{
that.__construct = Structr.modifiers.m_override(that, '__construct', function()
{
this._super.apply(this, arguments);
});
}
else
if(!that.__construct)
{
that.__construct = function() {};
}
//copy
for(var property in from.__construct)
{
if(from.__construct[property]['static'] && !that[property])
{
that.__construct[property] = from.__construct[property];
}
}
var propertyName;
//apply the static props
for(propertyName in that)
{
var value = that[propertyName];
//if the value is static, then tack it onto the constructor
if (value && value['static'])
{
that.__construct[propertyName] = value;
delete that[propertyName];
}
}
return that;
}
//really.. this isn't the greatest idea if a LOT of objects
//are being allocated in a short perioud of time. use the closure
//method instead. This is great for objects which are instantiated ONCE, or a couple of times :P.
Structr.fh = function (that)
{
that = Structr.extend({}, that);
//deprecated
that.getMethod = function (property)
{
return Structr.getMethod(this, property);
}
that.extend = function ()
{
return Structr.extend.apply(null, [this].concat(arguments))
}
//copy to target object
that.copyTo = function (target, lite)
{
Structr.copy(this, target, lite);
}
//wraps the objects methods so this always points to the right place
that.wrap = function(property)
{
return Structr.wrap(this, property);
}
return that;
}
module.exports = Structr;
+8
View File
@@ -0,0 +1,8 @@
var Structr=function(a,c){c||(c=Structr.fh({}));var b=Structr.extend(c,a);if(!b.__construct)b.__construct=function(){};b.__construct.prototype=b;b.__construct.extend=function(a){return Structr(a,b)};return b.__construct};Structr.copy=function(a,c){c||(c={});for(var b in a){var d=a[b],e=c[b];typeof d=="object"?(e=e&&d instanceof e.constructor?e:d instanceof Array?[]:{},Structr.copy(d,e)):e=d;c[b]=e}return c};Structr.getMethod=function(a,c){return function(){return a[c].apply(a,arguments)}};
Structr.findProperties=function(a,c){var b=[],d;for(d in a){var e=a[d];e&&e[c]&&b.push(d)}return b};Structr.getNArgs=function(a){return(a=a.toString().replace(/\{[\W\S]+\}/g,"").match(/\w+(?=[,\)])/g))?a.length:0};Structr.getFuncsByNArgs=function(a,c){return a.__private["overload::"+c]||(a.__private["overload::"+c]={})};Structr.getOverloadedMethod=function(a,c,b){return Structr.getFuncsByNArgs(a,c)[b]};
Structr.setOverloadedMethod=function(a,c,b,d){a=Structr.getFuncsByNArgs(a,c);if(b.overloaded)return a;a[d||Structr.getNArgs(b)]=b;return a};
Structr.modifiers={m_override:function(a,c,b){var d=a.__private&&a.__private[c]||a[c]||function(){},e=d;if(d.overloaded)var g=Structr.getNArgs(b),e=Structr.getOverloadedMethod(a,c,g);var h=function(){this._super=e;var a=b.apply(this,arguments);delete this._super;return a};if(d.overloaded)return Structr.modifiers.m_overload(a,c,h,g);return h},m_explicit:function(a,c,b){var d="_gs::"+c;typeof b!="object"&&(b={});if(!b.get)b.get=function(){return this._value};if(!b.set)b.set=function(b){this._value=
b};return function(a){if(arguments.length){if(this[d]!=a)this._value=this[d],b.set.apply(this,[a]),this[d]=this._value}else{this._value=this[d];var c=b.get.apply(this);delete this._value;return c}}},m_implicit:function(a,c,b){a.__private[c]=b;a.__defineGetter__(c,b);a.__defineSetter__(c,b)},m_overload:function(a,c,b,d){var e=Structr.setOverloadedMethod(a,c,b,d),a=function(){return e[arguments.length].apply(this,arguments)};a.overloaded=!0;return a}};
Structr.extend=function(a,c){c||(c={});var b={__private:{propertyModifiers:{}}};c instanceof Function&&(c=c());Structr.copy(a,b);var d={},e;for(e in c){var g=c[e],h=e.split(" "),f=h.pop(),i=b.__private.propertyModifiers[f]||(b.__private.propertyModifiers[f]=[]);if(h.length){for(var j={},k=h.length;k--;){var l=h[k];j["m_"+h[k]]=1;i.indexOf(l)==-1&&i.push(l)}if(j.m_explicit||j.m_implicit)g=Structr.modifiers.m_explicit(b,f,g);j.m_override&&(g=Structr.modifiers.m_override(b,f,g));if(j.m_implicit){Structr.modifiers.m_implicit(b,
f,g);continue}}for(h=i.length;h--;)g[i[h]]=!0;d[f]&&(i=b[f],i.overloaded||Structr.modifiers.m_overload(b,f,i,void 0),g=Structr.modifiers.m_overload(b,f,g,void 0));d[f]=1;b.__private[f]=b[f]=g}if(b.__construct&&a.__construct&&b.__construct==a.__construct)b.__construct=Structr.modifiers.m_override(b,"__construct",function(){this._super.apply(this,arguments)});for(f in b)if((g=b[f])&&g["static"])b.__construct[f]=g,delete b[f];return b};
Structr.fh=function(a){a=Structr.extend({},a);a.getMethod=function(a){return Structr.getMethod(this,a)};a.extend=function(a){return Structr.extend(this,a)};a.copyTo=function(a){Structr.copy(this,a)};return a};if(this.exports)exports.Structr=Structr;