Commits

firefly committed f7edc54

Ported Eldis to JavaScript.

  • Participants
  • Parent commits 2e4f929

Comments (0)

Files changed (16)

deps/JSONSelect/JSONSelect.md

-**WARNING**: This document is a work in progress, just like JSONSelect itself.
-View or contribute to the latest version [on github](http://github.com/lloyd/JSONSelect/blob/master/JSONSelect.md)
-
-# JSONSelect
-
-  1. [introduction](#introduction)
-  1. [levels](#levels)
-  1. [language overview](#overview)
-  1. [grouping](#grouping)
-  1. [selectors](#selectors)
-  1. [pseudo classes](#pseudo)
-  1. [expressions](#expressions)
-  1. [combinators](#combinators)
-  1. [grammar](#grammar)
-  1. [conformance tests](#tests)
-  1. [references](#references)
-
-## Introduction<a name="introduction"></a>
-
-JSONSelect defines a language very similar in syntax and structure to
-[CSS3 Selectors](http://www.w3.org/TR/css3-selectors/).  JSONSelect
-expressions are patterns which can be matched against JSON documents.
-
-Potential applications of JSONSelect include:
-
-  * Simplified programmatic matching of nodes within JSON documents.
-  * Stream filtering, allowing efficient and incremental matching of documents.
-  * As a query language for a document database.
-
-## Levels<a name="levels"></a>
-
-The specification of JSONSelect is broken into three levels.  Higher
-levels include more powerful constructs, and are likewise more
-complicated to implement and use.
-
-**JSONSelect Level 1** is a small subset of CSS3.  Every feature is
-derived from a CSS construct that directly maps to JSON.  A level 1
-implementation is not particularly complicated while providing basic
-querying features.
-
-**JSONSelect Level 2** builds upon Level 1 adapting more complex CSS
-constructs which allow expressions to include constraints such as
-patterns that match against values, and those which consider a node's
-siblings.  Level 2 is still a direct adaptation of CSS, but includes
-constructs whose semantic meaning is significantly changed.
-
-**JSONSelect Level 3** adds constructs which do not necessarily have a
-direct analog in CSS, and are added to increase the power and convenience
-of the selector language.  These include aliases, wholly new pseudo
-class functions, and more blue sky dreaming.
-
-## Language Overview<a name="overview"></a>
-
-<table>
-<tr><th>pattern</th><th>meaning</th><th>level</th></tr>
-<tr><td>*</td><td>Any node</td><td>1</td></tr>
-<tr><td>T</td><td>A node of type T, where T is one string, number, object, array, boolean, or null</td><td>1</td></tr>
-<tr><td>T.key</td><td>A node of type T which is the child of an object and is the value its parents key property</td><td>1</td></tr>
-<tr><td>T."complex key"</td><td>Same as previous, but with property name specified as a JSON string</td><td>1</td></tr>
-<tr><td>T:root</td><td>A node of type T which is the root of the JSON document</td><td>1</td></tr>
-<tr><td>T:nth-child(n)</td><td>A node of type T which is the nth child of an array parent</td><td>1</td></tr>
-<tr><td>T:nth-last-child(n)</td><td>A node of type T which is the nth child of an array parent counting from the end</td><td>2</td></tr>
-<tr><td>T:first-child</td><td>A node of type T which is the first child of an array parent (equivalent to T:nth-child(1)</td><td>1</td></tr>
-<tr><td>T:last-child</td><td>A node of type T which is the last child of an array parent (equivalent to T:nth-last-child(1)</td><td>2</td></tr>
-<tr><td>T:only-child</td><td>A node of type T which is the only child of an array parent</td><td>2</td></tr>
-<tr><td>T:empty</td><td>A node of type T which is an array or object with no child</td><td>2</td></tr>
-<tr><td>T U</td><td>A node of type U with an ancestor of type T</td><td>1</td></tr>
-<tr><td>T > U</td><td>A node of type U with a parent of type T</td><td>1</td></tr>
-<tr><td>T ~ U</td><td>A node of type U with a sibling of type T</td><td>2</td></tr>
-<tr><td>S1, S2</td><td>Any node which matches either selector S1 or S2</td><td>1</td></tr>
-<tr><td>T:has(S)</td><td>A node of type T which has a child node satisfying the selector S</td><td>3</td></tr>
-<tr><td>T:expr(E)</td><td>A node of type T with a value that satisfies the expression E</td><td>3</td></tr>
-<tr><td>T:val(V)</td><td>A node of type T with a value that is equal to V</td><td>3</td></tr>
-<tr><td>T:contains(S)</td><td>A node of type T with a string value contains the substring S</td><td>3</td></tr>
-</table>
-
-## Grouping<a name="grouping"></a>
-
-## Selectors<a name="selectors"></a>
-
-## Pseudo Classes<a name="pseudo"></a>
-
-## Expressions<a name="expressions"></a>
-
-## Combinators<a name="combinators"></a>
-
-## Grammar<a name="grammar"></a>
-
-(Adapted from [CSS3](http://www.w3.org/TR/css3-selectors/#descendant-combinators) and
- [json.org](http://json.org/))
-
-    selectors_group
-      : selector [ `,` selector ]*
-      ;
-
-    selector
-      : simple_selector_sequence [ combinator simple_selector_sequence ]*
-      ;
-
-    combinator
-      : `>` | \s+
-      ;
-
-    simple_selector_sequence
-      /* why allow multiple HASH entities in the grammar? */
-      : [ type_selector | universal ]
-        [ class | pseudo ]*
-      | [ class | pseudo ]+
-      ;
-
-    type_selector
-      : `object` | `array` | `number` | `string` | `boolean` | `null`
-      ;
-
-    universal
-      : '*'
-      ;
-
-    class
-      : `.` name
-      | `.` json_string
-      ;
-
-    pseudo
-      /* Note that pseudo-elements are restricted to one per selector and */
-      /* occur only in the last simple_selector_sequence. */
-      : `:` pseudo_class_name
-      | `:` nth_function_name `(` nth_expression `)`
-      | `:has` `(`  selectors_group `)`
-      | `:expr` `(`  expr `)`
-      | `:contains` `(`  json_string `)`
-      | `:val` `(` val `)`
-      ;
-
-    pseudo_class_name
-      : `root` | `first-child` | `last-child` | `only-child`
-
-    nth_function_name
-      : `nth-child` | `nth-last-child`
-
-    nth_expression
-      /* expression is and of the form "an+b" */
-      : TODO
-      ;
-
-    expr
-      : expr binop expr
-      | '(' expr ')'
-      | val
-      ;
-
-    binop
-      : '*' | '/' | '%' | '+' | '-' | '<=' | '>=' | '$='
-      | '^=' | '*=' | '>' | '<' | '=' | '!=' | '&&' | '||'
-      ;
-
-    val
-      : json_number | json_string | 'true' | 'false' | 'null' | 'x'
-      ;
-
-    json_string
-      : `"` json_chars* `"`
-      ;
-
-    json_chars
-      : any-Unicode-character-except-"-or-\-or-control-character
-      |  `\"`
-      |  `\\`
-      |  `\/`
-      |  `\b`
-      |  `\f`
-      |  `\n`
-      |  `\r`
-      |  `\t`
-      |   \u four-hex-digits
-      ;
-
-    name
-      : nmstart nmchar*
-      ;
-
-    nmstart
-      : escape | [_a-zA-Z] | nonascii
-      ;
-
-    nmchar
-      : [_a-zA-Z0-9-]
-      | escape
-      | nonascii
-      ;
-
-    escape
-      : \\[^\r\n\f0-9a-fA-F]
-      ;
-
-    nonascii
-      : [^\0-0177]
-      ;
-
-## Conformance Tests<a name="tests"></a>
-
-See [https://github.com/lloyd/JSONSelectTests](https://github.com/lloyd/JSONSelectTests)
-
-## References<a name="references"></a>
-
-In no particular order.
-
-  * [http://json.org/](http://json.org/)
-  * [http://www.w3.org/TR/css3-selectors/](http://www.w3.org/TR/css3-selectors/)
-  * [http://ejohn.org/blog/selectors-that-people-actually-use/](http://ejohn.org/blog/selectors-that-people-actually-use/)
-  * [http://shauninman.com/archive/2008/05/05/css\_qualified\_selectors](  * http://shauninman.com/archive/2008/05/05/css_qualified_selectors)
-  * [http://snook.ca/archives/html\_and\_css/css-parent-selectors](http://snook.ca/archives/html_and_css/css-parent-selectors)
-  * [http://remysharp.com/2010/10/11/css-parent-selector/](http://remysharp.com/2010/10/11/css-parent-selector/)
-  * [https://github.com/jquery/sizzle/wiki/Sizzle-Home](https://github.com/jquery/sizzle/wiki/Sizzle-Home)

deps/JSONSelect/LICENSE

-Copyright (c) 2011, Lloyd Hilaiel <lloyd@hilaiel.com>
-
-Permission to use, copy, modify, and/or distribute this software for any
-purpose with or without fee is hereby granted, provided that the above
-copyright notice and this permission notice appear in all copies.
-
-THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

deps/JSONSelect/README.md

+JSONSelect is *EXPERIMENTAL*, *ALPHA*, etc.
+
+JSONSelect defines a selector language similar to CSS intended for
+JSON documents.  For an introduction to the project see
+[jsonselect.org](http://jsonselect.org) or the [documentation](https://github.com/lloyd/JSONSelect/blob/master/JSONSelect.md).
+
+## Project Overview
+
+JSONSelect is an attempt to create a selector language similar to
+CSS for JSON objects.  A couple key goals of the project's include:
+
+  * **intuitive** - JSONSelect is meant to *feel like* CSS, meaning a developers with an understanding of CSS can probably guess most of the syntax.
+  * **expressive** - As JSONSelect evolves, it will include more of the most popular constructs from the CSS spec and popular implementations (like [sizzle](http://sizzlejs.com/)).  A successful result will be a good balance of simplicity and power.
+  * **language independence** - The project will avoid features which are unnecessarily tied to a particular implementation language.
+  * **incremental adoption** - JSONSelect features are broken in to conformance levels, to make it easier to build basic support and to allow incremental stabilization of the language.
+  * **efficient** - As many constructs of the language as possible will be able to be evaluated in a single document traversal.  This allows for efficient stream filtering.
+
+JSONSelect should make common operations easy, complex operations possible,
+but haughtily ignore weird shit.
+
+## What's Here
+
+This repository is the home to many things related to JSONSelect:
+
+  * [Documentation](https://github.com/lloyd/JSONSelect/blob/master/JSONSelect.md) which describes the language
+  * The [jsonselect.org](http://jsonselect.org) [site source](https://github.com/lloyd/JSONSelect/blob/master/site/)
+  * A [reference implementation](https://github.com/lloyd/JSONSelect/blob/master/src/jsonselect.js) in JavaScript
+
+## Related projects
+
+Conformance tests are broken out into a [separate
+repository](https://github.com/lloyd/JSONSelectTests) and may be used
+by other implementations.

deps/JSONSelect/package.json

-{
-  "author": "Lloyd Hilaiel <lloyd@hilaiel.com> (http://lloyd.io)",
-  "name": "JSONSelect",
-  "description": "CSS-like selectors for JSON",
-  "version": "0.2.2",
-  "homepage": "http://jsonselect.org",
-  "repository": {
-    "type": "git",
-    "url": "https://github.com/lloyd/JSONSelect.git"
-  },
-  "main": "src/jsonselect",
-  "engines": {
-    "node": ">=0.4.7"
-  },
-  "dependencies": {},
-  "devDependencies": {},
-  "files": [
-    "src/jsonselect.js",
-    "src/test/run.js",
-    "src/test/tests",
-    "tests",
-    "README.md",
-    "JSONSelect.md",
-    "package.json",
-    "LICENSE"
-  ],
-  "scripts": {
-    "test": "node src/test/run.js"
-  }
-}

src/bot.coffee

-fs      = require 'fs'
-vm      = require 'vm'
-net     = require 'net'
-
-bs      = require './buffered-socket'
-modserv = require './remote-module-server'
-irc     = require './irc'
-{Shell} = require './shell'
-
-#### Setup IRC connection ###########################################
-server = new irc.Server { hostname : "chat.freenode.net" }
-shell  = new Shell
-
-server.connection.on 'connect', ->
-	server.connection.authenticate "Eldis", "eldis", "Eldflugan"
-
-server.connection.on '001', ->
-	server.connection.write "JOIN", "##FireFly"
-
-server.connection.on 'PRIVMSG', (sender, target, message) ->
-	if message.match /^!/
-		try
-			shell.exec message[1..], (err, res) ->
-				server.connection.write "PRIVMSG", target, res.join(" ")
-
-		catch err
-			server.connection.write "PRIVMSG", target, "Error: #{err.message}"
-			console.log err.stack
-
-#### Setup remote module server #####################################
-moduleServer = new modserv.ModuleServer
-	hostname : 'localhost'
-	shell    : shell
-
-#### Create bot object ##############################################
-bot =
-	modules: []
-	shell: shell
-
-	addCommand: (name, func) ->
-		shell.commands[name] = func
-
-	reloadModule: (name) ->
-		fs.readFile "modules/#{name}.coffee", (err, res) ->
-			throw err if err
-			code = require('coffee-script').compile res.toString()
-
-			ctx =
-				require  : require
-				console  : console
-				bot      : bot
-				commands : {}
-
-				#### Command 'decorators' ###############################
-				manual : (fn) ->
-					fn.manualEnd = true
-					fn
-			
-			try
-				vm.runInNewContext code, ctx, name
-				bot.addCommand key, func for key,func of ctx.commands ? {}
-
-			catch err
-				console.warn "--- Error while loading module '#{name}'!"
-				throw err
-
-	loadModule: (name) ->
-		@modules.push name
-		@reloadModule name
-
-bot.loadModule 'core'
-
+var fs      = require('fs')
+  , vm      = require('vm')
+  , net     = require('net')
+
+  , bs      = require('./buffered-socket')
+  , modserv = require('./remote-module-server')
+  , irc     = require('./irc')
+  , shell   = require('./shell')
+
+//-- Setup IRC connection -------------------------------------------
+var server = new irc.Server("FreeNode", {
+	hostname: "chat.freenode.net",
+	userinfo: {
+		nick      : "Eldis",
+		ident     : "eldis",
+		realname  : "Eldflugan"
+	}
+})
+var shell = new shell.Shell
+
+server.connection.on('001', function() {
+	server.connection.write("JOIN", "##FireFly")
+})
+
+server.connection.on('PRIVMSG', function(sender, target, message) {
+	if (message.match(/^!/)) {
+		try {
+			shell.exec(message.slice(1), function(err, res) {
+				server.connection.write("PRIVMSG", target, res.join(" "))
+			})
+		} catch (err) {
+			server.connection.write("PRIVMSG", target, "Error: " + err.message)
+			console.log(err.stack)
+		}
+	}
+})
+
+//-- Setup remote module server -------------------------------------
+var moduleServer = new modserv.ModuleServer({
+	hostname : 'localhost',
+	shell    : shell
+})
+
+//-- Create bot object ----------------------------------------------
+var bot = {
+	modules: [],
+	shell: shell,
+
+	addCommand: function(name, func) {
+		shell.commands[name] = func
+	},
+
+	reloadModule: function(name) {
+		fs.readFile("modules/" + name + ".coffee", function(err, res) {
+			if (err) throw err
+			var code = require('coffee-script').compile(res.toString())
+
+			ctx = {
+				require  : require,
+				console  : console,
+				bot      : bot,
+				commands : {},
+
+				//-- Command 'decorators' ---------------------------
+				manual : function(fn) {
+					fn.manualEnd = true
+					return fn
+				}
+			}
+			
+			try {
+				vm.runInNewContext(code, ctx, name)
+
+				for (var key in ctx.commands || {}) {
+					bot.addCommand(key, ctx.commands[key])
+				}
+
+			} catch (err) {
+				console.warn("--- Error while loading module '" + name + "'!")
+				throw err
+			}
+		})
+	},
+
+	loadModule: function(name) {
+		this.modules.push(name)
+		this.reloadModule(name)
+	}
+}
+
+bot.loadModule('core')
+

src/buffered-socket.coffee

-events = require 'events'
-net    = require 'net'
-util   = require 'util'
-
-#### BufferedSocket #################################################
-BufferedSocket = (opts, socket = opts) ->
-	opts ?= {}
-	opts["separator"] ?= "\r\n"
-
-	@socket = socket
-	self = this
-
-	cache = ""
-	socket.on 'data', (chunk) ->
-		linesRaw = "" + cache + chunk
-
-		lines    = linesRaw.split opts["separator"]
-		cache    = lines.pop()
-
-		for line in lines
-			self.emit 'data', line
-	
-	socket.on 'connect', ->
-		self.emit 'connect'
-	
-	socket.on 'end', ->
-		self.emit 'end'
-
-	this
-
-util.inherits BufferedSocket, events.EventEmitter
-
-# delegate socket-related functions (e.g. write) to @socket.
-for key in Object.getOwnPropertyNames net.Socket.prototype
-	fn = net.Socket::[key]
-	continue if typeof fn != 'function'
-
-	do (key, fn) ->
-		BufferedSocket::[key] = ->
-			fn.apply @socket, arguments
-
-#### exports ########################################################
-exports.BufferedSocket = BufferedSocket
-exports.createSocket = (opts) ->
-	if not opts.hostname or not opts.port
-		throw new TypeError "Missing required option property: hostname or port."
-	
-	new BufferedSocket opts, new net.createConnection opts.port, opts.hostname
-

src/buffered-socket.js

+var events = require('events')
+  , net    = require('net')
+  , util   = require('util')
+
+//-- BufferedSocket -------------------------------------------------
+function BufferedSocket(opts, socket) {
+	if (!socket) {
+		socket = opts
+		opts   = {}
+	}
+
+	var self = this
+//	socket || (socket = opts)
+//	opts   || (opts = {})
+
+	opts["separator"] || (opts["separator"] = "\r\n")
+
+	this.socket = socket
+
+	var cache = ""
+	socket.on('data', function(chunk) {
+		var linesRaw = "" + cache + chunk
+
+		var lines = linesRaw.split(opts["separator"])
+		cache = lines.pop()
+
+		lines.forEach(function(line) {
+			self.emit('data', line)
+		})
+	})
+	
+	socket.on('connect', function() {
+		self.emit('connect')
+	})
+	
+	socket.on('end', function () {
+		self.emit('end')
+	})
+}
+
+//util.inherits(BufferedSocket, events.EventEmitter)
+util.inherits(BufferedSocket, net.Socket)
+
+// delegate socket-related functions (e.g. write) to this.socket.
+Object.getOwnPropertyNames(net.Socket.prototype).forEach(function(key) {
+	var fn = net.Socket.prototype[key]
+	if (typeof fn != 'function') {
+		return // Skip non-functions
+	}
+
+	void function(key, fn) {
+	//	BufferedSocket.prototype[key] = fn.bind(this.socket)
+		BufferedSocket.prototype[key] = function() {
+			return fn.apply(this.socket, arguments)
+		//	return net.Socket.prototype[key].apply(this, arguments)
+		}
+	}(key, fn)
+})
+
+//-- exports --------------------------------------------------------
+exports.BufferedSocket = BufferedSocket
+exports.createSocket = function(opts) {
+	if (!opts.hostname || !opts.port) {
+		throw new TypeError("Missing required option property: " +
+				"hostname or port.")
+	}
+	
+	return new BufferedSocket(opts,
+			new net.createConnection(opts.port, opts.hostname))
+}
+

src/client/client.coffee

-events = require 'events'
-
-bs     = require '../buffered-socket'
-
-conn = bs.createSocket
-	hostname : 'localhost'
-	port     : 13450
-
-send = (obj) ->
-	conn.write JSON.stringify obj
-	conn.write "\r\n"
-
-commands   = {}
-executions = {}
-
-conn.on 'data', (line) ->
-	data = JSON.parse line
-
-	switch data.type
-		when 'handshake'
-			console.log "Got handshake from server!"
-			send {
-				type     : 'handshake'
-				version  : '0.0.1'
-				metadata : {
-					name    : "Test module"
-					author  : "Jonas Höglund"
-					version : "0.0.1"
-				}
-			}
-
-			for name of commands
-				send { type:'register', name:name }
-
-		when 'execute'
-			console.log "Executing #{data.name}!"
-			ex = {}
-			ex.name      = data.name # FIXME: should be data.name?
-			ex.eid       = data.eid
-			ex.arguments = data.arguments
-
-			ex.stdin     = new events.EventEmitter
-			#ex.stdout    = new events.EventEmitter
-
-			ex.output = ->
-				for arg in arguments
-					send { type:'output', eid:@eid, value:arg }
-
-			ex.end = ->
-				ex.output.apply ex, arguments
-
-				send { type:'output-end', eid:@eid }
-
-				@output = ->
-				@end    = ->
-
-			executions[ex.eid] = ex
-			commands[ex.name].apply ex, [ex].concat data.arguments
-
-		when 'input'
-			console.log "Got input for #{data.eid}"
-
-			ex = executions[data.eid]
-			ex.stdin.emit 'data', data.value
-
-		when 'input-end'
-			console.log "End of input for #{data.eid}"
-
-			ex = executions[data.eid]
-			ex.stdin.emit 'end'
-
-addCommand = (name, func) ->
-	commands[name] = func
-
-	send { type:'register', name:name }
-
-addCommand '_echo', (exec, args...) ->
-	exec.end.apply exec, args
-
-addCommand '_testCommand', (exec) ->
-	exec.end "Hello from a remote module!"
-
-addCommand '_cat', (exec) ->
-	exec.stdin.on 'data', (data) ->
-		exec.output data
-	
-	exec.stdin.on 'end', ->
-		exec.end()
-
-addCommand '_seq', (exec, from, to) ->
-	if `to == null`
-		to   = from
-		from = 1
-	
-	for i in [from..to]
-		exec.output i
-
-	exec.end()
-

src/client/client.js

+var events = require('events')
+  , bs     = require('../buffered-socket')
+
+var conn = bs.createSocket({
+	hostname : 'localhost',
+	port     : 13450
+})
+
+function send(obj) {
+	conn.write(JSON.stringify(obj))
+	conn.write("\r\n")
+}
+
+var commands   = {}
+  , executions = {}
+
+conn.on('data', function(line) {
+	var data = JSON.parse(line)
+
+	// TODO: Refactor into looking up functions in an object.
+	switch (data.type) {
+		case 'handshake':
+			console.log("Got handshake from server!")
+			send({
+				type     : 'handshake',
+				version  : '0.0.1',
+				metadata : {
+					name    : "Test module",
+					author  : "Jonas Höglund",
+					version : "0.0.1"
+				}
+			})
+
+			for (var name in commands) {
+				send({ type:'register', name:name })
+			}
+
+			break
+
+		case 'execute':
+			console.log("Executing " + data.name + "!")
+			var ex = {}
+			ex.name      = data.name
+			ex.eid       = data.eid
+			ex.arguments = data.arguments
+
+			ex.stdin     = new events.EventEmitter()
+			//ex.stdout    = new events.EventEmitter
+
+			ex.output = function() {
+				var self = this
+				Array.prototype.forEach.call(arguments, function(arg) {
+					send({ type:'output', eid:self.eid, value:arg })
+				})
+			}
+
+			ex.end = function() {
+				ex.output.apply(ex, arguments)
+
+				send({ type:'output-end', eid:this.eid })
+
+				this.output = function() {}
+				this.end    = function() {}
+			}
+
+			executions[ex.eid] = ex
+			commands[ex.name].apply(ex, [ex].concat(data.arguments))
+
+			break
+
+		case 'input':
+			console.log("Got input for " + data.eid)
+
+			ex = executions[data.eid]
+			ex.stdin.emit('data', data.value)
+
+			break
+
+		case 'input-end':
+			console.log("End of input for " + data.eid)
+
+			ex = executions[data.eid]
+			ex.stdin.emit('end')
+
+			break
+	}
+})
+
+function addCommand(name, func) {
+	commands[name] = func
+
+	send({ type:'register', name:name })
+}
+
+addCommand('_echo', function(exec/*, ...args*/) {
+	exec.end.apply(exec, Array.prototype.slice.call(arguments, 1))
+})
+
+addCommand('_testCommand', function(exec) {
+	exec.end("Hello from a remote module!")
+})
+
+addCommand('_cat', function(exec) {
+	exec.stdin.on('data', function(data) {
+		exec.output(data)
+	})
+	
+	exec.stdin.on('end', exec.end.bind(exec))
+//	function() {
+//		exec.end()
+//	})
+})
+
+addCommand('_seq', function(exec, from, to) {
+	if (to == null) {
+		to   = from
+		from = 1
+	}
+	
+	for (var i=from; i<=to; i++) {
+		exec.output(i)
+	}
+
+	exec.end()
+})
+
+// @depends: soupselect
+addCommand('_g', function(exec/*, ...termparts*/) {
+	var termparts = Array.prototype.slice.call(arguments, 1)
+	var searchTerm = termparts.join("+").replace(/\s+/g, "+")
+
+	var http       = require('http')
+	  , soupselect = require('soupselect')
+	  , htmlparser = require('htmlparser')
+
+	http.get({
+		//host : 'google.com'
+		//path : "/search?q=#{searchTerm}"
+		host : 'reddit.com',
+		port : 80,
+		path : '/'
+	}, function(res) {
+		//res.setEncoding('utf8')
+
+		var buffer = []
+		res.on('data', function(chunk) {
+			console.log("Got data!", chunk.toString())
+			buffer.push(chunk)
+		})
+
+		res.on('end', function() {
+			var handler = new htmlparser.DefaultHandler(function(err, dom) {
+				if (err) throw err
+
+				console.log("dom", dom)
+				var titles = soupselect.select(dom, 'a.title')
+				console.log("titles", titles)
+
+				console.log("### Test ###")
+
+				titles.forEach(function(title) {
+					console.log("* " + title.children[0].raw + " [" +
+							title.attribs.href + "]")
+				})
+
+				exec.end("Done!")
+			})
+
+			var parser = new htmlparser.Parser(handler)
+			parser.parseComplete(buffer.join(""))
+		})
+
+		exec.stdin.on('end', function() {
+			exec.end("Done!")
+		})
+	})
+})

src/irc.coffee

-{EventEmitter} = require 'events'
-util = require 'util'
-net  = require 'net'
-
-bs   = require './buffered-socket'
-
-#### Helper functions ###############################################
-validateChannelName = (name) ->
-	if not name.match /^[#&][^,]*$/
-		throw new Error "Invalid channel name: '#{name}'"
-
-rawExpression = ///
-	^
-	(?: :([^\x20]+) \x20 |)           # : (user!ident@host)
-	([^:] [^\x20]*)                   # command
-	((?: \x20 [^\x20:] [^\x20]* )*)   # parameters except last
-	\x20
-	(?: : (.*)                        # last parameter: either colon + rest of string
-		| ([^\x20]*))                 # ...or multiple non-space characters (like other params)
-	$
-///
-
-userExpression = ///
-	(       [^!]+ )       #   (nick )
-	(?: ! ( [^@]+ ) )?    # ! (ident)
-	(?: @ (    .+ ) )?    # @ (host )
-///
-
-parseLine = (line) ->
-	matches = line.match rawExpression
-	throw new Error "Invalid line: '#{line}'." if not matches
-
-	[_, userData, command, argsData, lastArg, lastArg2] = matches
-	lastArg ?= lastArg2
-
-	user = if userData?
-			[_, nick, ident, host] = userData.match userExpression
-			{nick, ident, host}
-		else
-			{ nick : "<server>" }
-	
-	user.toString = -> @nick
-
-	args = [lastArg]
-	if argsData != ""
-		args = argsData[1..].split(" ").concat args
-	
-	{ user, command, arguments: args }
-
-#### Connection #####################################################
-Connection = (opts) ->
-	opts.port ?= 6667
-	@options = opts
-
-	this
-
-util.inherits Connection, EventEmitter
-
-Connection::connect = ->
-	EventEmitter.call this
-
-	conn = this
-	socket = @socket = bs.createSocket
-		hostname  : @options.hostname
-		port      : @options.port
-		separator : "\r\n"
-	
-	#socket.connect()
-
-	socket.on 'data', (lineRaw) ->
-		line = parseLine lineRaw.trimLeft()
-		#conn.emit 'raw', line
-
-		args = [line.command, line.user].concat line.arguments
-		EventEmitter::emit.apply conn, args
-	
-	socket.on 'connect', =>
-		@emit 'connect'
-	
-	socket.on 'end', =>
-		@emit 'disconnect'
-
-Connection::authenticate = (nick, ident, realname) ->
-	@write "NICK", nick
-	@write "USER", ident, "-", "-", realname
-
-isArrayLike = (arr) ->
-	typeof arr == 'object' and arr.length?
-
-Connection::write = (cmdAndArgs..., lastArg) ->
-	@sendRaw raw = cmdAndArgs.concat([":" + lastArg]).join(" ")
-
-Connection::sendRaw = (cmd, args) ->
-	if cmd.match /[\r\n]/
-		throw new Error "Trying to send more than one raw command at the same time!"
-	else
-		console.log "> #{cmd}"
-		@socket.write "#{cmd}\r\n"
-
-#### Server #########################################################
-Server = (name, opts) ->
-	@connection = conn = new Connection opts
-
-	@name     = name
-	@channels = {}
-	@users    = {}
-	
-	conn.connect()
-
-	conn.on 'PING', (sender, message) ->
-		conn.write "PONG", message
-	
-	conn.on 'PRIVMSG', (sender, target, message) ->
-		console.log "[#{target}] <#{sender}> #{message}"
-
-	conn.on 'NOTICE', (sender, target, message) ->
-		console.log "[#{target}] [NOTICE] <#{sender}> #{message}"
-
-	conn.on 'JOIN', (sender, channel) ->
-		console.log "[#{channel}] #{sender} joined channel #{channel}"
-
-	conn.on 'PART', (sender, channel, message = "") ->
-		console.log "[#{channel}] #{sender} parted from channel #{channel} (#{message})"
-
-	conn.on 'KICK', (sender, channel, kickee, message = "") ->
-		console.log "[#{channel}] #{sender} kicked #{kickee} from channel #{channel} (#{message})"
-
-	conn.on 'QUIT', (sender, message = "") ->
-		console.log "User #{sender} has quit IRC (#{message})"
-
-	conn.on 'NICK', (sender, newNick) ->
-		console.log "User #{sender} changed nick to #{newNick}"
-
-	conn.on '001', (sender, user, message) ->
-		console.log "[Welcome] #{message}"
-
-	conn.on '002', (sender, user, message) ->
-		console.log "[Host] #{message}"
-
-	conn.on '003', (sender, user, message) ->
-		console.log "[Created] #{message}"
-
-	conn.on '004', (sender, user, host, version, usermodes, chanmodes, supported...) ->
-		console.log "[My Info] I am #{host} running #{version}. " +
-				"User modes: #{usermodes}. Channel modes: #{chanmodes}"
-
-	conn.on '005', (sender, user, message...) ->
-		console.log "[Supported] #{message}"
-
-	conn.on '250', (sender, user, message) ->
-		console.log "[Statistics] #{message}"
-	
-	this
-
-#### exports ########################################################
-exports.Connection = Connection
-exports.Server     = Server
+var events = require('events')
+  , util   = require('util')
+  , net    = require('net')
+
+  , bs     = require('./buffered-socket')
+
+/*
+{EventEmitter} = require 'events'
+util = require 'util'
+net  = require 'net'
+
+bs   = require './buffered-socket'
+*/
+
+//-- Helper functions -----------------------------------------------
+function validateChannelName(name) {
+	if (!name.match(/^[#&][^,]*$/)) {
+		throw new Error("Invalid channel name: '" + name + "'")
+	}
+}
+
+/*
+rawExpression = ///
+	^
+	(?: :([^\x20]+) \x20 |)           # : (user!ident@host)
+	([^:] [^\x20]*)                   # command
+	((?: \x20 [^\x20:] [^\x20]* )*)   # parameters except last
+	\x20
+	(?: : (.*)                        # last parameter: either colon + rest of string
+		| ([^\x20]*))                 # ...or multiple non-space characters (like other params)
+	$
+/// */
+
+/*
+userExpression = ///
+	(       [^!]+ )       #   (nick )
+	(?: ! ( [^@]+ ) )?    # ! (ident)
+	(?: @ (    .+ ) )?    # @ (host )
+/// */
+
+var rawExpression =
+		/^(?::([^ ]+) |)([^:][^ ]*)((?: [^ :][^ ]*)*) (?::(.*)|([^ ]*))$/
+
+  , userExpression = /([^!]+)(?:!([^@]+))?(?:@(.+))?/
+
+
+function parseLine(line) {
+	var matches = line.match(rawExpression)
+	if (!matches) throw new Error("Invalid line: '" + line + "'.")
+
+	var userData  = matches[1] // user!ident@host
+	  , command   = matches[2]
+	  , argsData  = matches[3] // All parameters except the last one.
+	  , lastArg   = matches[4] || matches[5]
+
+	// Build an object representing the user data.
+	if (userData) {
+		var matches = userData.match(userExpression)
+		  , nick   = matches[1]
+		  , ident  = matches[2]
+		  , host   = matches[3]
+
+		var user = { nick:nick, ident:ident, host:host }
+	} else {
+		var user = { nick:"<server>" }
+	}
+	
+	user.toString = function() {return this.nick}
+
+	// Parameters
+	var args = [lastArg]
+	if (argsData) {
+		args = argsData.slice(1).split(" ").concat(args)
+	}
+	
+	return {
+		user       : user,
+		command    : command,
+		arguments  : args
+	}
+}
+
+//-- Connection -----------------------------------------------------
+function Connection(opts) {
+	opts.port || (opts.port = 6667)
+	this.options = opts
+}
+
+util.inherits(Connection, events.EventEmitter)
+
+Connection.prototype.connect = function() {
+	events.EventEmitter.call(this)
+	var self = this
+
+	this.socket = bs.createSocket({
+		hostname  : this.options.hostname,
+		port      : this.options.port,
+		separator : "\r\n"
+	})
+	
+	//socket.connect()
+
+	this.socket.on('data', function(lineRaw) {
+		var line = parseLine(lineRaw.trimLeft())
+		//conn.emit 'raw', line
+
+		var args = [line.command, line.user].concat(line.arguments)
+		events.EventEmitter.prototype.emit.apply(self, args)
+	})
+	
+	this.socket.on('connect', function() {
+		self.emit('connect')
+
+		if (self.options.userinfo) {
+			var ui = self.options.userinfo
+			self.authenticate(ui.nick, ui.ident, ui.realname)
+		}
+	})
+	
+	this.socket.on('end', function() {
+		self.emit('disconnect')
+	})
+}
+
+Connection.prototype.authenticate = function(nick, ident, realname) {
+	this.write("NICK", nick)
+	this.write("USER", ident, "-", "-", realname)
+}
+
+Connection.prototype.write = function(/* command, ...args */) {
+	var args    = Array.prototype.slice.call(arguments)
+	  , lastArg = args.pop()
+
+	this.sendRaw(args.concat([":" + lastArg]).join(" "))
+	//@sendRaw raw = cmdAndArgs.concat([":" + lastArg]).join(" ")
+}
+
+Connection.prototype.sendRaw = function(cmd, args) {
+	if (cmd.match(/[\r\n]/)) {
+		throw new Error("Trying to send more than one raw command at "
+				+ "the same time!")
+	} else {
+		console.log("> " + cmd)
+		this.socket.write(cmd + "\r\n")
+	//	@socket.write "#{cmd}\r\n"
+	}
+}
+
+//-- Server ---------------------------------------------------------
+function Server(name, opts) {
+	var conn = this.connection = new Connection(opts)
+
+	this.name     = name
+	this.channels = {}
+	this.users    = {}
+	
+	conn.connect()
+
+	conn.on('PING', function(sender, message) {
+		conn.write("PONG", message)
+	})
+	conn.on('PRIVMSG', function(sender, target, message) {
+		console.log("[%s] <%s> %s", target, sender, message)
+	//	console.log("[#{target}] <#{sender}> #{message}")
+	})
+
+	conn.on('NOTICE', function(sender, target, message) {
+		console.log("[%s] [NOTICE] <%s> %s", target, sender, message)
+	//	console.log("[#{target}] [NOTICE] <#{sender}> #{message}")
+	})
+
+	conn.on('JOIN', function(sender, channel) {
+		console.log("[%s] %s joined channel %s", channel, sender, channel)
+	//	console.log("[#{channel}] #{sender} joined channel #{channel}")
+	})
+
+	conn.on('PART', function(sender, channel, message) {
+		message || (message = "")
+		console.log("[%s] %s parted from channel %s (%s)", channel, sender,
+				channel, message)
+	//	console.log("[#{channel}] #{sender} parted from channel #{channel} (#{message})")
+	})
+
+	conn.on('KICK', function(sender, channel, kickee, message) {
+		message || (message = "")
+		console.log("[%s] %s kicked %s from channel %s (%s)", channel, sender,
+				kickee, channel, message)
+		//console.log("[#{channel}] #{sender} kicked #{kickee} from channel #{channel} (#{message})")
+	})
+
+	conn.on('QUIT', function(sender, message) {
+		message || (message = "")
+		console.log("User %s has quit IRC (%s)", sender, message)
+	//	console.log("User #{sender} has quit IRC (#{message})")
+	})
+
+	conn.on('NICK', function(sender, newNick) {
+		console.log("User %s changed nick to %s", sender, newNick)
+	//	console.log("User #{sender} changed nick to #{newNick}")
+	})
+
+	conn.on('001', function(sender, user, message) {
+		console.log("[Welcome] %s", message)
+	//	console.log("[Welcome] #{message}")
+	})
+
+	conn.on('002', function(sender, user, message) {
+		console.log("[Host] %s", message)
+	//	console.log("[Host] #{message}")
+	})
+
+	conn.on('003', function(sender, user, message) {
+		console.log("[Created] %s", message)
+	//	console.log("[Created] #{message}")
+	})
+
+	conn.on('004', function(sender, user, host, version, usermodes, chanmodes
+			/*, ...supported*/) {
+		var supported = Array.prototype.slice.call(arguments, 6)
+		console.log("[My info] I am %s runnig %s. User modes: %s. Channel modes: %s",
+				host, version, usermodes, chanmodes)
+	//	console.log "[My Info] I am #{host} running #{version}. " +
+	//			"User modes: #{usermodes}. Channel modes: #{chanmodes}"
+	})
+
+	conn.on('005', function(sender, user/*, ...message*/) {
+		var message = Array.prototype.slice.call(arguments, 2)
+		console.log("[Supported] %s", message)
+	//	console.log("[Supported] #{message}")
+	})
+
+	conn.on('250', function(sender, user, message) {
+		console.log("[Statistics] %s", message)
+	//	console.log("[Statistics] #{message}")
+	})
+}
+
+//-- exports --------------------------------------------------------
+exports.Connection = Connection
+exports.Server     = Server

src/remote-module-server.coffee

-assert = require 'assert'
-net    = require 'net'
-
-bs     = require './buffered-socket'
-
-#### Module #########################################################
-Module = (uid, socket, server) ->
-	@uid    = uid
-	@socket = new bs.BufferedSocket socket
-	@server = server
-
-	self = this
-
-	@eidCounter = 0
-	@executions = {}
-	@commands   = []
-
-	@metadata = {
-		name    : '<unknown>'
-		author  : '<unknown>'
-		version : '<unknown>'
-	}
-
-	@socket.on 'data', (line) ->
-		try
-			data = JSON.parse line
-			console.log "#{self}: #{data.type}:", data
-
-		catch err
-			console.log "Got invalid data from #{self}:"
-			console.log line.toString()
-			self.send {
-				type  : 'error'
-				value : "Your request couldn't be processed."
-			}
-
-		switch data.type
-			when 'handshake'
-				console.log "Module #{self} shook hands"
-				console.log "   Module metadata:", data.metadata
-
-				for k,v of data.metadata
-					self.metadata[k] = v
-
-			when 'register'
-				console.log "Module #{self} registered command #{data.name}."
-				self.commands.push data.name
-
-				server.addCommand data.name, (exec, args...) ->
-					eid = (++self.eidCounter)
-					self.executions[eid] = exec
-
-					exec.stdin.on 'data', (data) ->
-						self.send {
-							type  : 'input'
-							eid   : eid
-							value : data
-						}
-
-					exec.stdin.on 'end', ->
-						self.send {
-							type : 'input-end'
-							eid  : eid
-						}
-
-					self.send {
-						type      : 'execute'
-						eid       : eid
-						name      : data.name
-						arguments : args
-					}
-
-			when 'output'
-				console.log "Module #{self} outputted #{data.value}."
-				self.executions[data.eid].output data.value
-
-			when 'output-end'
-				console.log "Module #{self} ended output!"
-				self.executions[data.eid].end()
-	
-	@socket.on 'end', ->
-		console.log "Module #{self} received 'end'."
-		self.server.removeModule self
-	
-	this
-
-Module::toString = ->
-	"[#{@uid}:#{@metadata.name}]"
-
-Module::send = (obj) ->
-	@socket.write JSON.stringify obj
-	@socket.write "\r\n"
-
-#### ModuleServer ###################################################
-ModuleServer = (opts) ->
-	self = this
-	assert.ok opts and opts.hostname and opts.shell, "Missing required " +
-			"property in ModuleServer options; either 'hostname' or 'shell'."
-	
-	@addCommand = (name, func) ->
-		func.manualEnd = true
-		opts.shell.commands[name] = func
-	
-	@removeCommand = (name) ->
-		delete opts.shell.commands[name]
-
-	@uidCounter = 0
-	@modules    = {}
-
-	server = net.createServer (socket) ->
-		module = new Module (++self.uidCounter), socket, self
-		self.addModule module
-
-	server.listen opts.port or 13450
-	this
-
-ModuleServer::addModule = (module) ->
-	console.log "Adding module #{module}"
-	@modules[module.uid] = module
-
-	module.send {
-		type    : 'handshake'
-		version : '0.0.1'
-		uid     : module.uid
-	}
-
-ModuleServer::removeModule = (module) ->
-	console.log "Removing module #{module}"
-	@removeCommand name for name in module.commands
-	delete @modules[module.uid]
-
-#### exports ########################################################
-exports.Module       = Module
-exports.ModuleServer = ModuleServer
-

src/remote-module-server.js

+var assert = require('assert')
+  , net    = require('net')
+  , bs     = require('./buffered-socket')
+
+//-- Module ---------------------------------------------------------
+function Module(uid, socket, server) {
+	this.uid    = uid
+	this.socket = new bs.BufferedSocket(socket)
+	this.server = server
+
+	var self = this
+
+	this.eidCounter = 0
+	this.executions = {}
+	this.commands   = []
+
+	this.metadata = {
+		name    : '<unknown>',
+		author  : '<unknown>',
+		version : '<unknown>'
+	}
+
+	this.socket.on('data', function(line) {
+		try {
+			var data = JSON.parse(line)
+		//	console.log(self + ": " + data.type + ":", data)
+			console.log("%s: %s:", self, data.type, data)
+		} catch (err) {
+		//	console.log("Got invalid data from " + self + ":")
+			console.log("Got invalid data from %s:", self)
+			console.log(line.toString())
+			self.send({
+				type  : 'error',
+				value : "Your request couldn't be processed."
+			})
+		}
+
+		switch (data.type) {
+			case 'handshake':
+				console.log("Module " + self + " shook hands")
+				console.log("   Module metadata:", data.metadata)
+
+				// Copy provided metadata.
+				for (var key in data.metadata) {
+					self.metadata[key] = data.metadata[key]
+				}
+
+				break
+
+			case 'register':
+				console.log("Module " + self + " registered command "
+						+ data.name + ".")
+				self.commands.push(data.name)
+
+				server.addCommand(data.name, function(exec/*, ...args*/) {
+					var args = Array.prototype.slice.call(arguments, 1)
+					  , eid = (++self.eidCounter)
+
+					self.executions[eid] = exec
+
+					exec.stdin.on('data', function(data) {
+						self.send({
+							type  : 'input',
+							eid   : eid,
+							value : data
+						})
+					})
+
+					exec.stdin.on('end', function() {
+						self.send({
+							type : 'input-end',
+							eid  : eid
+						})
+					})
+
+					self.send({
+						type      : 'execute',
+						eid       : eid,
+						name      : data.name,
+						arguments : args
+					})
+				})
+				break
+
+			case 'output':
+			//	console.log("Module #{self} outputted #{data.value}.")
+				console.log("Module %s outputted %s.", self, data.value)
+				self.executions[data.eid].output(data.value)
+				break
+
+			case 'output-end':
+				console.log("Module " + self + " ended output!")
+				self.executions[data.eid].end()
+				break
+		}
+	})
+	
+	this.socket.on('end', function() {
+		console.log("Module " + self + " received 'end'.")
+		self.server.removeModule(self)
+	})
+}
+	
+
+Module.prototype.toString = function() {
+	return "[" + this.uid + ":" + this.metadata.name + "]"
+}
+
+Module.prototype.send = function(obj) {
+	this.socket.write(JSON.stringify(obj))
+	this.socket.write("\r\n")
+}
+
+//-- ModuleServer ---------------------------------------------------
+function ModuleServer(opts) {
+	self = this
+
+	assert.ok(opts && opts.hostname && opts.shell,
+			"Missing required property in ModuleServer options; either "
+			+ "'hostname' or 'shell'.")
+	
+	this.addCommand = function(name, func) {
+		func.manualEnd = true
+		opts.shell.commands[name] = func
+	}
+	
+	this.removeCommand = function(name) {
+		delete opts.shell.commands[name]
+	}
+
+	this.uidCounter = 0
+	this.modules    = {}
+
+	var server = net.createServer(function(socket) {
+		var module = new Module((++self.uidCounter), socket, self)
+		self.addModule(module)
+	})
+
+	server.listen(opts.port || 13450)
+}
+
+ModuleServer.prototype.addModule = function(module) {
+	console.log("Adding module " + module)
+	this.modules[module.uid] = module
+
+	module.send({
+		type    : 'handshake',
+		version : '0.0.1',
+		uid     : module.uid
+	})
+}
+
+ModuleServer.prototype.removeModule = function(module) {
+	console.log("Removing module " + module)
+
+	for (var name in module.commands) {
+		this.removeCommand(name)
+	}
+
+	delete this.modules[module.uid]
+}
+
+//-- exports --------------------------------------------------------
+exports.Module       = Module
+exports.ModuleServer = ModuleServer
+

src/shell.coffee

-# FireFly shell thing
-{EventEmitter} = require 'events'
-
-DEBUG_EXECUTIONS = false
-
-#### Lexer ##########################################################
-specialModesMap = {
-	"'" : 'str-single'
-	'"' : 'str-double'
-	"`" : 'str-eval'
-}
-
-lex = (str) ->
-	res       = []
-	mode      = 'none'
-	modeStart = 'none'
-	last      = 0
-
-	push = (type, value) ->
-		return if type == 'literal' and value == "" # Disallow empty literals
-
-		res.push
-			type  : type
-			value : value
-	
-	idx = 0
-	while idx < str.length
-		chr = str[idx]
-
-		if mode == 'none'
-			pushed = true
-
-			switch chr
-				when " "
-					push 'literal', str[last..idx-1]
-
-				when "="
-					push 'varname', str[last..idx-1]
-
-				when "|", ";"
-					push 'literal', str[last..idx-1]
-					push 'internal', str[idx]
-
-				when '"', "'", "`"
-					mode = specialModesMap[chr]
-
-					if str[idx-1] != " " and idx > 0
-						push 'unknown', str[last..idx-1]
-
-					#modeStart = mode
-
-				else
-					pushed = false
-
-			last = idx + 1 if pushed
-
-		else
-			if chr in "\"'`" and mode == specialModesMap[chr]
-				if mode == 'str-eval'
-					push 'eval', str[last..idx+1]
-					--idx
-				else
-					push 'quoted', str[last..idx-1]
-
-				mode = 'none'
-				last = idx + 2
-
-		++idx
-	
-	line = str[last..]
-	
-	push 'literal', line if line.length > 0
-
-	res
-
-#### Shell ##########################################################
-exports.Shell = Shell = (opts = {}) ->
-	@defaults =
-		input  : opts.input
-		output : opts.output or =>
-			Array::push.apply @outputBuffer, arguments
-
-	@outputBuffer = []
-	
-	@variables = opts.variables or {}
-	@commands  = opts.commands  or {}
-	
-	@commands["echo"] ?= -> @output.apply @, arguments
-	@commands["cat"]  ?= -> @output.apply @, @input
-	
-	@
-
-Shell::createExecution = ->
-	res = {}
-
-	res.stdin  = new EventEmitter
-	res.stdout = new EventEmitter
-
-	#res.input  = res.stdin
-	res.output = ->
-		@stdout.emit 'data', data for data in arguments
-	
-	res
-
-#### Shell#process ##################################################
-Shell::process = (tokens) ->
-	res = []
-	curr = @createExecution()
-	mode = 'none'
-	
-	for {type,value} in tokens
-		if mode == 'none' and type == 'literal'
-			curr.command = value
-			curr.args    = []
-			mode = 'args'
-
-		else if mode == 'args'
-			if type == 'literal' or type == 'quoted'
-				curr.args.push value
-
-			else if type == 'internal'
-				if value == ";"
-					res.push curr
-					curr = @createExecution()
-					mode = 'none'
-
-				else if value == "|"
-					newExecution = @createExecution()
-					curr.stdout = new EventEmitter
-
-					if DEBUG_EXECUTIONS
-						#### DEBUG ##################################
-						do (curr) ->
-							curr.stdout.on 'data', (data) ->
-								console.log "#{curr.command} -> #{newExecution.command} [data]:", data
-								newExecution.stdin.emit 'data', data
-					
-							curr.stdout.on 'end', ->
-								console.log "#{curr.command} -> #{newExecution.command} [end]"
-								newExecution.stdin.emit 'end'
-						#### /DEBUG #################################
-
-					else
-						curr.stdout = newExecution.stdin
-
-					res.push curr
-					curr = newExecution
-					mode = 'none'
-
-			else
-				throw new Error "Syntax error: near #{value}"
-
-		else
-			throw new Error "Syntax error: near #{value}"
-
-	
-	res.push curr
-	res
-
-#### Shell#exec ##################################################
-Shell::exec = (str, callback) ->
-	executions   = @process lex str
-	first        = executions[0]
-	last         = executions[-1..][0]
-	outputBuffer = []
-
-	console.log "==== executions ===="
-	console.log executions
-
-	# No commands, nothing to do...
-	if executions.length == 0
-		callback null, []
-		return
-	
-	# Listen on stdout on last command; we want to catch output and
-	# send to channel.
-	last.stdout.on 'data', (str) ->
-		console.log "#{last.command} -> <output> [data]:", str if DEBUG_EXECUTIONS
-		outputBuffer.push str
-	
-	last.stdout.once 'end', ->
-		console.log "#{last.command} -> <output> [end]" if DEBUG_EXECUTIONS
-		callback null, outputBuffer
-
-	# Starts execution of one command.
-	execute = (ex) =>
-		console.log "Begin", ex.command if DEBUG_EXECUTIONS
-		func = @commands[ex.command]
-
-		if not func
-			throw new Error "Command not found: #{ex.command}"
-
-		ex.end = ->
-			console.log "End", ex.command if DEBUG_EXECUTIONS
-			@output.apply this, arguments
-			@stdout.emit 'end'
-
-			# Clean up functions.
-			@end    = ->
-			@output = ->
-
-		if not func.manualEnd
-			ex.stdin.on 'end', ->
-				ex.end()
-
-		# The process.nextTick is necessary for "asynchronous" behaviour
-		#process.nextTick ->
-		->
-			func.apply ex, [ex].concat ex.args
-
-	funcs = (execute ex for ex in executions)
-	do func for func in funcs.reverse()
-	first.stdin.emit 'end'
-
+// FireFly shell thing
+var events = require('events')
+
+var DEBUG_EXECUTIONS = false
+
+//-- Lexer ----------------------------------------------------------
+var specialModesMap = {
+	"'" : 'str-single',
+	'"' : 'str-double',
+	"`" : 'str-eval'
+}
+
+function lex(str) {
+	var res       = []
+	  , mode      = 'none'
+	  , modeStart = 'none'
+	  , last      = 0
+
+	function push(type, value) {
+		if (type == 'literal' && value == "") {
+			return // Silently ignore empty literals
+		}
+		//return if type == 'literal' and value == "" # Disallow empty literals
+
+		res.push({
+			type  : type,
+			value : value
+		})
+	}
+	
+	var idx = 0
+	while (idx < str.length) {
+		var chr = str[idx]
+
+		if (mode == 'none') {
+			var pushed = true
+
+			switch (chr) {
+				case " ":
+					push('literal', str.slice(last, idx))
+					break
+
+				case "=":
+					push('varname', str.slice(last, idx))
+					break
+
+				case "|": case ";":
+					push('literal', str.slice(last, idx))
+					push('internal', str[idx])
+					break
+
+				case '"': case "'": case "`":
+					mode = specialModesMap[chr]
+
+					if (idx > 0 && str[idx-1] != " ") {
+						push('unknown', str.slice(last, idx))
+					}
+
+					//modeStart = mode
+					break
+
+				default:
+					pushed = false
+			}
+
+			if (pushed) {
+				last = idx + 1
+			}
+		} else {
+			if (chr in "\"'`" && mode == specialModesMap[chr]) {
+				if (mode == 'str-eval') {
+					push('eval', str.slice(last, idx+2))
+					--idx
+				} else {
+					push('quoted', str.slice(last, idx))
+				}
+
+				mode = 'none'
+				last = idx + 2
+			}
+		}
+
+		++idx
+	}
+	
+	line = str.slice(last)
+	
+	if (line.length > 0) {
+		push('literal', line)
+	}
+
+	return res
+}
+
+//-- Shell ----------------------------------------------------------
+var Shell = exports.Shell = function(opts) {
+	opts || (opts = {})
+	var self = this
+
+	var output = opts.output || function() {
+		Array.prototype.push.apply(self.outputBuffer, arguments)
+	}
+
+
+	this.defaults = {
+		input  : opts.input,
+		output : output
+	}
+
+	this.outputBuffer = []
+	
+	this.variables = opts.variables || {}
+	this.commands  = opts.commands  || {}
+	
+	this.commands["echo"] || (this.commands["echo"] = output.bind(this))
+	this.commands["cat"]  || (this.commands["cat"] =
+			function() { output.apply(self, self.input) })
+}
+
+Shell.prototype.createExecution = function() {
+	var res = {}
+
+	res.stdin  = new events.EventEmitter()
+	res.stdout = new events.EventEmitter()
+
+	//res.input  = res.stdin
+	res.output = function() {
+		Array.prototype.forEach.call(arguments, function(data) {
+			res.stdout.emit('data', data)
+		})
+	}
+	
+	return res
+}
+
+//-- Shell#process --------------------------------------------------
+Shell.prototype.process = function(tokens) {
+	var self = this
+	  , res  = []
+	  , curr = this.createExecution()
+	  , mode = 'none'
+	
+	tokens.forEach(function(token) {
+		var type  = token.type
+		  , value = token.value
+
+		if (mode == 'none' && type == 'literal') {
+			curr.command = value
+			curr.args    = []
+			mode         = 'args'
+
+		} else if (mode == 'args') {
+			if (type == 'literal' || type == 'quoted') {
+				curr.args.push(value)
+
+			} else if (type == 'internal') {
+				if (value == ";") {
+					res.push(curr)
+					curr = this.createExecution()
+					mode = 'none'
+
+				} else if (value == "|") {
+					var newExecution = self.createExecution()
+					curr.stdout = new events.EventEmitter()
+
+					if (DEBUG_EXECUTIONS) {
+						//-- DEBUG ----------------------------------
+						void function(curr) {
+							curr.stdout.on('data', function(data) {
+						//		console.log "#{curr.command} -> #{newExecution.command} [data]:", data
+								newExecution.stdin.emit('data', data)
+							})
+					
+							curr.stdout.on('end', function() {
+						//		console.log "#{curr.command} -> #{newExecution.command} [end]"
+								newExecution.stdin.emit('end')
+							})
+						}(curr)
+						//-- DEBUG ----------------------------------
+
+					} else {
+						// Bind the processes together--the 'pipe' part.
+						curr.stdout = newExecution.stdin
+					}
+
+					res.push(curr)
+					curr = newExecution
+					mode = 'none'
+				}
+
+			} else {
+				throw new Error("Syntax error: near " + value)
+			}
+
+		} else {
+			throw new Error("Syntax error: near " + value)
+		}
+	})
+
+	
+	res.push(curr)
+	return res
+}
+
+//-- Shell#exec -----------------------------------------------------
+Shell.prototype.exec = function(str, callback) {
+	var executions   = this.process(lex(str))
+	  , first        = executions[0]
+	  , last         = executions.slice(-1)[0]
+	  , outputBuffer = []
+
+	var self = this
+
+	console.log("==== executions ====")
+	console.log(executions)
+	console.log()
+
+	// No commands, nothing to do...
+	if (executions.length == 0) {
+		callback(null, [])
+		return
+	}
+	
+	// Listen on stdout on last command; we want to catch output and
+	// send to channel.
+	last.stdout.on('data', function(str) {
+		if (DEBUG_EXECUTIONS) {
+			console.log(last.command + " -> <output> [data]:", str)
+		}
+		outputBuffer.push(str)
+	})
+	
+	last.stdout.once('end', function() {
+		if (DEBUG_EXECUTIONS) {
+			console.log(last.command + " -> <output> [end]")
+		}
+		callback(null, outputBuffer)
+	})
+
+	// Starts execution of one command.
+	function execute(ex) {
+		if (DEBUG_EXECUTIONS) {
+			console.log("Begin", ex.command)
+		}
+		var func = self.commands[ex.command]
+
+		if (!func) {
+			throw new Error("Command not found: " + ex.command)
+		}
+
+		ex.end = function() {
+			if (DEBUG_EXECUTIONS) {
+				console.log("End", ex.command)
+			}
+
+			this.output.apply(this, arguments)
+			this.stdout.emit('end')
+
+			// Clean up functions.
+			this.end    = function() {}
+			this.output = function() {}
+		}
+
+		if (!func.manualEnd) {
+			ex.stdin.on('end', function() {
+				ex.end()
+			})
+		}
+
+		// The process.nextTick is necessary for "asynchronous" behaviour
+		//process.nextTick ->
+		return function() {
+			func.apply(ex, [ex].concat(ex.args))
+		}
+	}
+
+	var funcs = executions.map(execute)
+	funcs.reverse().forEach(function(func) {
+		func()
+	})
+
+	first.stdin.emit('end')
+}
+