Commits

firefly committed 2ebffd9

Committing various fixes from the VPS.

  • Participants
  • Parent commits ae21b34

Comments (0)

Files changed (14)

 #!/bin/bash
 
+# Runner script for Eldis4, preparing PWD and performing the logging of stdout
+# to log file.
+
 oldpath="$PWD"
-eldispath="`readlink -f $0 | sed 's|/[^/]*$||'`"
+eldispath="$(dirname $(readlink -f $0))"
 
 cd "$eldispath"
-
-#harmonyFlags="--harmony_typeof --harmony_proxies --harmony_weakmaps --harmony_block_scoping"
-
-node --harmony "$@" src/eldis4.js | tee eldis4.log
-RT=$?
-
+node --harmony src/eldis4.js "$@" | tee eldis4.log
 cd "$oldpath"
-exit $RT	# Exit value from running Eldis4.

File modules/05-commands.coffee

 					return
 				
 				supDigitsMap = ("\u2070\u00b9\u00b2\u00b3\u2074\u2075\u2076" +
-							    "\u2077\u2078\u2079").split ""
+				                "\u2077\u2078\u2079").split ""
 				supDigitsMap["-"] = "\u207b"
+
+				subDigitsMap = ("\u2080\u2081\u2082\u2083\u2084\u2085\u2086" +
+				                "\u2087\u2088\u2089").split("")
+				subDigitsMap["+"] = "\u208a"
+				subDigitsMap["-"] = "\u208b"
 				
 				beautify = (str) ->
 					str = str.replace /<sup>([^<]+)<\/sup>/g, (_, value) ->
 							value.replace /./g, (chr) -> supDigitsMap[chr]
 						else
 							"^{#{value}}"
+
+					str = str.replace /<sub>([^<]+)<\/sub>/g, (_, value) ->
+						if value.match /^-?\d+$/
+							value.replace /./g, (chr) -> subDigitsMap[chr]
+						else
+							"_{#{value}}"
 					
 					deentify str
 				

File modules/05-interpreters.js

 function escapeString(str) {
   str = String(str).split(/[\r\n]/).join(" ")
 
-  return str.length <= 150
-       ? str
-       : str.slice(0, 147) + " [\u2026]"
+  return str.length <= 150? str
+       : /* otherwise */    str.slice(0, 147) + " [\u2026]"
 }
 
 // @optdepends jsdom - DOM support in the JS context.
           , ctor  = proto.constructor
 
         return isNativeErrorFun(ctor) && ctor.prototype == proto
+         // && (isGetter(obj, 'message') ? getGetter(obj, 'message') == getGetter(proto, 'message') : true)
+         // && (isGetter(obj, 'name')    ? getGetter(obj, 'name')    == getGetter(proto, 'name')    : true)
+
+        function getGetter(obj, prop) {
+          var pd = Object.getOwnPropertyDescriptor(obj, prop)
+          return pd && pd.get
+        }
+        function isGetter(obj, prop) { return getGetter(obj, prop) != null }
       }
     }
   }
 
     fs.writeFile(tmpfile, data.line, function(err) {
       if (err) {
-        this.send(data.target, "Error: " + err)
+        self.send(data.target, "Error: " + err)
         return
       }
 
     })
   }
 })
-*/
+//*/
+
+// Assumes stuff/migol.jar exists and is a proper executable Migol interpreter.
+bot.triggers.command.add('migol', {
+  help: "Evaluates a Migol program.",
+  exec: function(data) {
+    var self    = this
+      , options = {timeout:15000, killSignal:'SIGKILL'}
+      , tmpfile = '/tmp/tmp-eldis4-migol'
+      , runner  = '/usr/bin/java'
+      , args    = ['-jar', 'stuff/migol.jar', tmpfile]
+
+
+    fs.writeFile(tmpfile, data.line, function(err) {
+      if (err) {
+        self.send(data.target, "Error: " + err)
+        return
+      }
+
+      child_process.execFile(runner, args, options, getExecResponder({}, self, data))
+   // child_process.exec(command, options, getExecResponder(self, data))
+    })
+  }
+})
 
 // Assumes stuff/z.jar exists and is a proper executable Z interpreter.
 bot.triggers.command.add('z', {
   help: "Evaluates a Z program.",
   exec: function(data) {
     var self    = this
-      , options = {timeout:30000, killSignal:'SIGKILL'}
+      , options = {timeout:15000, killSignal:'SIGKILL'}
       , suffix  = Math.random()
       , tmpfile = '/tmp/tmp-eldis4-z-' + suffix
 
-   // , command = "java -jar stuff/Z3.jar -w '[' ']' '" + tmpfile + "'"
-   // , command = "stuff/z/runner.sh '" + tmpfile + "' '" + suffix + "'"
-
       , runner  = 'stuff/z/runner.sh'
       , args    = [tmpfile, suffix]
 

File modules/05-unicode-lookup/index.js

   , child_process = require('child_process')
 
 function lookup(chr, callback) {
+  var proc = child_process.spawn('java', ['-cp', moduleReldir, 'CharInfo', chr])
+
+  var errData = []
+  proc.stderr.on('data', function (data) {
+    errData.push(data)
+  })
+
+  var outData = []
+  proc.stdout.on('data', function (data) {
+    outData.push(data)
+  })
+
+  proc.once('exit', function (code, signal) {
+    if (code == null) {
+      // Handle if process got killed
+      var prefix = signal || "stderr"
+        , stderr = errData.join("")
+
+      callback(new Error("killed: " + prefix + ": " + stderr))
+
+    } else if (code != 0) {
+      // Abnormal exit code
+      var stderr = errData.join("")
+      callback(new Error("stderr: " + stderr))
+
+    } else {
+      // Handle normal exit
+      var stdout = outData.join("")
+      callback(null, JSON.parse(stdout))
+    }
+  })
+
+  /*
   child_process.exec('java -cp "' + moduleReldir + '" CharInfo "' + chr + '"',
         function(err, stdout, stderr) {
     if (err) return callback(err)
 
     callback(null, JSON.parse(stdout))
   })
+  */
 }
 
 bot.triggers.command.add('char', {
           stopped = true
           return
 
-      }// else if (chr.length != 1) {
-      //    self.send(data.target, "Error: Must check exactly one character!")
-      //    return
-      //}
+      }
 
       lookup(chr, function(err, charinfo) {
           if (err) throw err

File modules/05-wikipedia/index.js

-
 var modulePrefix = '../modules/05-wikipedia/'
 
-var wikiParser  = require(modulePrefix + 'parser')
+console.log(require.cache)
+delete require.cache['/home/firefly/prog/eldis4/modules/05-wikipedia/substitutors.js']
+delete require.cache['/home/firefly/prog/eldis4/modules/05-wikipedia/stringifier.js']
+delete require.cache['/home/firefly/prog/eldis4/modules/05-wikipedia/parser.js']
+// /home/firefly/prog/eldis4/modules/05-wikipedia/substitutors.js
+
+var util        = require('util')
+  , wikiParser  = require(modulePrefix + 'parser')
   , stringifier = require(modulePrefix + 'stringifier')
   , substitutor = require(modulePrefix + 'substitutors')['wikipedia']
 
       }
 
       // Add the wikipedia URL to the response
-      var niceURL = "<http://en.wikipedia.org/wiki/" + encodedTitle + ">"
+      var niceURL = util.format("<http://%s.wikipedia.org/wiki/%s>",
+                                subdomain, encodedTitle)
+   // var niceURL = "<http://" + subdomain + ".wikipedia.org/wiki/" + encodedTitle + ">"
       if (niceURL.length > 130) {
         niceURL = "[URL too long]"
       }

File modules/05-wikipedia/parser.js

 //-- block-level ("beginning of line") syntax -----------------------
 // Blank lines (para delimiters)
 blockLevel.push(function blank(parser, lines) {
-	if (lines[0].match(/^\s*$/)) {
-		// Substitute blank lines with nothing
-		return [ 1, [] ]
-	}
+  if (lines[0].match(/^\s*$/)) {
+    // Substitute blank lines with nothing
+    return [ 1, [] ]
+  }
 })
 
 // Headings
 blockLevel.push(function heading(parser, lines) {
-	var match
-	if (match = lines[0].match(/^(=+)\s*(.*)\s*\1$/)) {
-		var level   = match[1].length // # of '='s
-		  , heading = match[2]
+  var match
+  if (match = lines[0].match(/^(=+)\s*(.*)\s*\1$/)) {
+    var level   = match[1].length // # of '='s
+      , heading = match[2]
 
-		return [ 1, [
-			{ name    : 'heading'
-			, level   : level
-			, content : [ heading ]
-			} ]]
-	}
+    return [ 1, [ { name:    'heading',
+                    level:   level,
+                    content: [ heading ] } ]]
+  }
 })
 
 // Horizontal ruler
 blockLevel.push(function hr(parser, lines) {
-	if (lines[0].match(/^----+$/)) { // four or more hyphens
-		return [ 1, [ { name:'hr', content:[] } ] ]
-	}
+  if (lines[0].match(/^----+$/)) { // four or more hyphens
+    return [ 1, [ { name:    'hr',
+                    content: [] } ]]
+  }
 })
 
 // Preformatted text
 blockLevel.push(function pre(parser, lines) {
-	for (var i=0; i<lines.length && lines[i].match(/^ /); i++);
+  for (var i=0; i<lines.length && lines[i].match(/^ /); i++);
 
-	var content = lines.slice(0, i).map(function(s) {
-			return s.slice(1) }).join("\n")
+  var content = lines.slice(0, i).map(function(s) {
+      return s.slice(1) }).join("\n")
 
-	if (i > 0) {
-		return [ i, [
-			{ name    : 'pre'
-			, content : [ content ]
-			} ]]
-	}
+  if (i > 0) {
+    return [ i, [
+      { name    : 'pre'
+      , content : [ content ]
+      } ]]
+  }
 })
 
 // Lists
 blockLevel.push(function list(parser, lines) {
-	var match
-	// First make sure that we actually have the beginning of a list...
-	if (!(match = lines[0].match(/^[*#:]/))) { return null }
+  var match
+  // First make sure that we actually have the beginning of a list...
+  if (!(match = lines[0].match(/^[*#:]/))) { return null }
 
-	// Now, we only want to match lines with the same type of bullet
-	var bulletPattern = new RegExp("^\\" + match[0])
-	for (var i=1; i<lines.length && lines[i].match(bulletPattern); i++);
+  // Now, we only want to match lines with the same type of bullet
+  var bulletPattern = new RegExp("^\\" + match[0])
+  for (var i=1; i<lines.length && lines[i].match(bulletPattern); i++);
 
-	var content = lines.slice(0, i).map(function(s) {
-		return s.replace(/^[*#:]\s*/, '')
-	}).map(function(str) {
-		return (
-			{ name    : 'list-node'
-			, type    : match[0]
-			, content : parser.parseInline(str)
-			})
-	})
+  var content = lines.slice(0, i).map(function(s) {
+    return s.replace(/^[*#:]\s*/, '')
+  }).map(function(str) {
+    return (
+      { name    : 'list-node'
+      , type    : match[0]
+      , content : parser.parseInline(str)
+      })
+  })
 
-	if (i > 0) {
-		return [ i, [
-			{ name    : 'list'
-			, type    : match[0]
-			, content : content
-			} ]]
-	}
+  if (i > 0) {
+    return [ i, [
+      { name    : 'list'
+      , type    : match[0]
+      , content : content
+      } ]]
+  }
 })
 
 // Tables (ew)
 blockLevel.push(function table(parser, lines) {
-	if (!lines[0].match(/^{\|/)) { return null }
-	for (var i=1; i<lines.length && !lines[i].match(/\|}/); i++);
+  if (!lines[0].match(/^{\|/)) { return null }
+  for (var i=1; i<lines.length && !lines[i].match(/\|}/); i++);
 
-	var withoutTable = lines.slice(0, i).join("\n").replace(/^{\|.*?\|}/, '')
+  var withoutTable = lines.slice(0, i).join("\n").replace(/^{\|.*?\|}/, '')
 
-	return [ i+1, [
-		{ name    : 'para'
-		, content : parser.parseInline(withoutTable)
-		} ]]
+  return [ i+1, [
+    { name    : 'para'
+    , content : parser.parseInline(withoutTable)
+    } ]]
 })
 
 // "Block-level templates" -- a bit hacky
 /*
 blockLevel.push(function table(parser, lines) {
-	if (!lines[0].match(/^{{/)) { return null }
-	for (var i=0; i<lines.length && !lines[i].match(/}}/); i++);
+  if (!lines[0].match(/^{{/)) { return null }
+  for (var i=0; i<lines.length && !lines[i].match(/}}/); i++);
 
-	var content = parser.parseInline(lines.slice(0, i).join("\n"))
+  var content = parser.parseInline(lines.slice(0, i).join("\n"))
 
-	return [ i+1, [
-		{ name    : 'para'
-		, content : content
-		} ]]
+  return [ i+1, [
+    { name    : 'para'
+    , content : content
+    } ]]
 })
 //*/
 
 
 //-- Inline ("middle-of-line") syntax -------------------------------
 function parseLinkRef(str) {
-	// TODO: handle stuff like namespaces, categories, ...
-	return str
+  // TODO: handle stuff like namespaces, categories, ...
+  return str
 }
 
 // Formatting
 inlineLevel.push(function emphasis(parser, str) {
-	var match
-	if (match = str.match(/^('''?)(.*?)\1(?!')/)) {
-		var length  = match[0].length
-		  , type    = match[1].length == 3 ? 'bold' : 'italic'
-		  , content = parser.parseInline(match[2])
+  var match
+  if (match = str.match(/^('''?)(.*?)\1(?!')/)) {
+    var length  = match[0].length
+      , type    = match[1].length == 3 ? 'bold' : 'italic'
+      , content = parser.parseInline(match[2])
 
-		return [ length, [
-			{ name    : type
-			, content : content
-			} ]]
-	}
+    return [ length, [
+      { name    : type
+      , content : content
+      } ]]
+  }
 })
 
 // Wikilink & template abstraction
 function getPushArgFunc(args, allowNamedArgs) {
-	if (allowNamedArgs) {
-		// Parses "named args", i.e. 'foo=bar'
-		return function(str) {
-			var key = args.length
-			  , match
+  if (allowNamedArgs) {
+    // Parses "named args", i.e. 'foo=bar'
+    return function(str) {
+      var key = args.length
+        , match
 
-			str = str.trim()
+      str = str.trim()
 
-			if (match = str.match(/^([^\s ]+)=(.*)$/)) {
-				key = match[1]
-				str = match[2]
-			}
+      if (match = str.match(/^([^\s ]+)=(.*)$/)) {
+        key = match[1]
+        str = match[2]
+      }
 
-			args[key] = str
-		}
+      args[key] = str
+    }
 
-	} else {
-		// Pushes args to the array regardless of equal signs.
-		return function(str) {
-			args.push(str)
-		}
-	}
+  } else {
+    // Pushes args to the array regardless of equal signs.
+    return function(str) {
+      args.push(str)
+    }
+  }
 }
 
 function regexEscape(str) {
-	return str.replace(/[\[\]\+\*]/g, '\\$&')
+  return str.replace(/[\[\]\+\*]/g, '\\$&')
 }
 
 function getWikilinkTemplateFunc(delimTokens, allowNamedArgs) {
-	var beginPattern = new RegExp("^" + regexEscape(delimTokens[0]))
-	  , endPattern   = new RegExp("^" + regexEscape(delimTokens[1]))
-	  , nodeName     = delimTokens[2]
+  var beginPattern = new RegExp("^" + regexEscape(delimTokens[0]))
+    , endPattern   = new RegExp("^" + regexEscape(delimTokens[1]))
+    , nodeName     = delimTokens[2]
 
-	return function(parser, str) {
-		// First, check that we're actually starting a (template|wikilink)
-		if (!beginPattern.test(str)) { return null }
+  return function(parser, str) {
+    // First, check that we're actually starting a (template|wikilink)
+    if (!beginPattern.test(str)) { return null }
 
-		var level   = 1
-		  , last    = 2
-		  , args    = []
-		  , pushArg = getPushArgFunc(args, allowNamedArgs)
+    var level   = 1
+      , last    = 2
+      , args    = []
+      , pushArg = getPushArgFunc(args, allowNamedArgs)
 
-		// Find the matching }} of our template... and also fetch "arguments"
-		// inside the {{ }}. Similarly with [[ ]] for wikilinks.
-		for (var i=2; level>0 && i<str.length; i++) {
-			var substr = str.slice(i)
+    // Find the matching }} of our template... and also fetch "arguments"
+    // inside the {{ }}. Similarly with [[ ]] for wikilinks.
+    for (var i=2; level>0 && i<str.length; i++) {
+      var substr = str.slice(i)
 
-			if (beginPattern.test(substr)) {
-				level++; i++
-			} else if (endPattern.test(substr)) {
-				level--; i++
-			} else if (substr[0] == '|' && level == 1) {
-				pushArg(str.slice(last, i))
-				last = i+1
-			}
-		}
-		pushArg(str.slice(last, i-2))
+      if (beginPattern.test(substr)) {
+        level++; i++
+      } else if (endPattern.test(substr)) {
+        level--; i++
+      } else if (substr[0] == '|' && level == 1) {
+        pushArg(str.slice(last, i))
+        last = i+1
+      }
+    }
+    pushArg(str.slice(last, i-2))
 
-		var target = args.shift()
+    var target = args.shift()
 
-		// Parse the arguments
-		for (var key in args) {
-			args[key] = parser.parseInline(args[key])
-		}
+    // Parse the arguments
+    for (var key in args) {
+      args[key] = parser.parseInline(args[key])
+    }
 
-		return [ i, [
-			{ name    : nodeName
-			, target  : target
-			, args    : args
-			} ]]
-	}
+    return [ i, [
+      { name    : nodeName
+      , target  : target
+      , args    : args
+      } ]]
+  }
 }
 
 inlineLevel.push(getWikilinkTemplateFunc(['[[', ']]', 'wikilink'], false))
 inlineLevel.push(getWikilinkTemplateFunc(['{{', '}}', 'template'], true))
 
-// Silly SGML-inspired tags
+// SGML tags
 inlineLevel.push(function tags(parser, str) {
-	var match
-	if (match = str.match(/^<([^ >]+)([^>]*)>(.*?)<\/\1>/)
-	          || str.match(/^<([^ >]+)([^>]*)()\/>/)) {
-		var length  = match[0].length
-		  , tag     = match[1]
-		  , attrs   = match[2]
-	//	  , content = [ match[3] ] // note: plaintext
-		  , content = parser.parseInline(match[3])
+  var match
+  if (match = str.match(/^<([^ >]+)([^>]*)>(.*?)<\/\1>/)
+            || str.match(/^<([^ >]+)([^>]*)()\/>/)) {
+    var length  = match[0].length
+      , tag     = match[1]
+      , attrs   = match[2]
+  //    , content = [ match[3] ] // note: plaintext
+      , content = parser.parseInline(match[3])
 
-		return [ length, [
-			{ name    : 'tag'
-			, tag     : tag
-			, attrs   : attrs
-			, content : content
-			} ]]
-	}
+    return [ length, [ { name:    'tag',
+                         tag:     tag,
+                         attrs:   attrs,
+                         content: content,
+                         raw:     match[3] } ]]
+  }
 })
 
 
 //-- Actual parsing function ----------------------------------------
 // Abstraction of block-level (line-by-line) and inline (char-by-char) parsing.
 function getParserLoopFunc(syntaxArr, betweenNodesFunc) {
-	return function(arr) {
-		var last   = 0
-		  , result = []
-		  , tmp
+  return function(arr) {
+    var last   = 0
+      , result = []
+      , tmp
 
-		for (var i=0; i<arr.length; i++) {
-			var subarr = arr.slice(i)
+    for (var i=0; i<arr.length; i++) {
+      var subarr = arr.slice(i)
 
-			for (var j=0; j<syntaxArr.length; j++) {
-				if (tmp = syntaxArr[j](this, subarr)) {
-					assert.ok(tmp[0] >= 0, "Can't skip negative amount of chars/lines!")
+      for (var j=0; j<syntaxArr.length; j++) {
+        if (tmp = syntaxArr[j](this, subarr)) {
+          assert.ok(tmp[0] >= 0, "Can't skip negative amount of chars/lines!")
 
-					var lastPart = arr.slice(last, i)
-					if (lastPart.length > 0) {
-						result.push(betweenNodesFunc(lastPart))
-					}
+          var lastPart = arr.slice(last, i)
+          if (lastPart.length > 0) {
+            result.push(betweenNodesFunc(lastPart))
+          }
 
-					result = result.concat(tmp[1])
+          result = result.concat(tmp[1])
 
-					last = i + tmp[0]
-					i    = last - 1 // -1 to account for the i++ in the loop.
-					break
-				}
-			}
-		}
+          last = i + tmp[0]
+          i    = last - 1 // -1 to account for the i++ in the loop.
+          break
+        }
+      }
+    }
 
-		if (last < arr.length) {
-			result.push(betweenNodesFunc(arr.slice(last)))
-		}
+    if (last < arr.length) {
+      result.push(betweenNodesFunc(arr.slice(last)))
+    }
 
-		return result
-	}
+    return result
+  }
 }
 
 var parser =
-	{ parseInline : getParserLoopFunc(inlineLevel, function(s) {return s})
-	, parseBlock  : getParserLoopFunc(blockLevel, function(lines) {
-		return (
-			{ name    : 'para'
-			, content : parser.parseInline(lines.join("\n"))
-			})
-		})
-	}
+  { parseInline : getParserLoopFunc(inlineLevel, function(s) {return s})
+  , parseBlock  : getParserLoopFunc(blockLevel, function(lines) {
+    return (
+      { name    : 'para'
+      , content : parser.parseInline(lines.join("\n"))
+      })
+    })
+  }
 
 // Takes a string and parses it completely.
 exports.parse = function(str, stripComments) {
-	if (stripComments) {
-		// /[\S\s]/ matches anything at all (like /./ but including newlines)
-		str = str.replace(/<!--[\S\s]*?-->\s*/g, '')
-	}
+  if (stripComments) {
+    // /[\S\s]/ matches anything at all (like /./ but including newlines)
+    str = str.replace(/<!--[\S\s]*?-->\s*/g, '')
+  }
 
-	var lines   = str.split("\n")
-	  , content = parser.parseBlock(lines)
+  var lines   = str.split("\n")
+    , content = parser.parseBlock(lines)
 
-	// Build up a heading tree for easy subsection indexing
-	var heading      = [ {caption:'null', level:1, content:[]} ]
-	  , headingStack = []
+  // Build up a heading tree for easy subsection indexing
+  var heading      = [ {caption:'null', level:1, content:[]} ]
+    , headingStack = []
 
-	content.forEach(function(node) {
-		if (node.name == 'heading') {
-			var newHeading =
-				[ { caption : node.content
-				  , level   : node.level
-				  , content : []
-				  } ]
+  content.forEach(function(node) {
+    if (node.name == 'heading') {
+      var newHeading =
+        [ { caption : node.content
+          , level   : node.level
+          , content : []
+          } ]
 
-			if (node.level <= heading[0].level) {
-				var diff = 1 + heading[0].level - node.level
-				for (var i=0; i<diff && headingStack.length>0; i++) {
-					heading = headingStack.pop()
-				}
-			}
+      if (node.level <= heading[0].level) {
+        var diff = 1 + heading[0].level - node.level
+        for (var i=0; i<diff && headingStack.length>0; i++) {
+          heading = headingStack.pop()
+        }
+      }
 
-			// Allow indexing either by caption # or caption name
-			heading.push(newHeading)
-			heading[newHeading[0].caption.join("")] = newHeading
+      // Allow indexing either by caption # or caption name
+      heading.push(newHeading)
+      heading[newHeading[0].caption.join("")] = newHeading
 
-			headingStack.push(heading)
-			heading = newHeading
+      headingStack.push(heading)
+      heading = newHeading
 
-		} else {
-			heading[0].content.push(node)
-		}
-	})
+    } else {
+      heading[0].content.push(node)
+    }
+  })
 
-	return ( // Time to return the actual 'root node'
-		{ name     : 'root'
-		, content  : content
-		, headings : headingStack[0] || heading
-		})
+  return ( // Time to return the actual 'root node'
+    { name     : 'root'
+    , content  : content
+    , headings : headingStack[0] || heading
+    })
 }
 

File modules/05-wikipedia/stringifier.js

   , RESET  = '\x0f'
   , COLOR  = '\x03'
 
-  , colorMap =
-	{ 'white'   : "00"
-	, 'black'   : "01"
-	, 'dblue'   : "02"
-	, 'dgreen'  : "03"
-	, 'red'     : "04"
-	, 'dred'    : "05"
-	, 'dpurple' : "06"
-	, 'yellow'  : "07"
-	, 'green'   : "08"
-	, 'dcyan'   : "09"
-	, 'cyan'    : "10"
-	, 'blue'    : "11"
-	, 'purple'  : "12"
-	, 'dgrey'   : "13"
-	, 'grey'    : "14"
-	}
+  , colorMap = { 'white'   : "00",
+                 'black'   : "01",
+                 'dblue'   : "02",
+                 'dgreen'  : "03",
+                 'red'     : "04",
+                 'dred'    : "05",
+                 'dpurple' : "06",
+                 'yellow'  : "07",
+                 'green'   : "08",
+                 'dcyan'   : "09",
+                 'cyan'    : "10",
+                 'blue'    : "11",
+                 'purple'  : "12",
+                 'dgrey'   : "13",
+                 'grey'    : "14" }
 
 module.exports = {
-	stringifyArr: function(arr, stringifiers) {
-		var self = this
+  stringifyArr: function(arr, stringifiers) {
+    var self = this
 
-		function constt(v) {return function() {return v}}
+    function constt(v) {return function() {return v}}
 
-		stringifiers || (stringifiers = {})
-		stringifiers.template || (stringifiers.template = constt("<<template>>"))
-		stringifiers.wikilink || (stringifiers.wikilink = constt(null))
+    stringifiers || (stringifiers = {})
+    stringifiers.template || (stringifiers.template = constt("<<template>>"))
+    stringifiers.wikilink || (stringifiers.wikilink = constt(null))
 
-		return arr.map(function(node) {
-			return self.stringify(node, stringifiers)
-		}).join("").trim()
-	},
+    return arr.map(function(node) {
+      return self.stringify(node, stringifiers)
+    }).join("").trim()
+  },
 
-	stringify: function(node, stringifiers) {
-		if (typeof node == 'string') { return node }
+  stringify: function(node, stringifiers) {
+    if (typeof node == 'string') { return node }
 
-		var self = this
+    var self = this
 
-		function st(arr) {
-			return self.stringifyArr(arr, stringifiers)
-		}
+    function st(arr) {
+      return self.stringifyArr(arr, stringifiers)
+    }
 
-		function wrapIn(format, str) {
-			if (format in colorMap) {
-				return COLOR + colorMap[format] + str + RESET
-			} else if (format == 'bold') {
-				return BOLD + str + RESET
-			} else if (format == 'italic') {
-				return ITALIC + str + RESET
-			} else {
-				throw new Error("Wikitext stringifier: unknown format: " + format)
-			}
-		}
+    function wrapIn(format, str) {
+      if (format in colorMap) {
+        return COLOR + colorMap[format] + str + RESET
+      } else if (format == 'bold') {
+        return BOLD + str + RESET
+      } else if (format == 'italic') {
+        return ITALIC + str + RESET
+      } else {
+        throw new Error("Wikitext stringifier: unknown format: " + format)
+      }
+    }
 
-		switch (node.name) {
-			case 'para':
-				return st(node.content) + "  "
+    switch (node.name) {
+      case 'para':
+        return st(node.content) + "  "
 
-			case 'heading':
-				var prefix  = Array(node.level + 1).join("#")
-				  , content = prefix + " " + st(node.content) + ":"
+      case 'heading':
+        var prefix  = Array(node.level + 1).join("#")
+          , content = prefix + " " + st(node.content) + ":"
 
-				return wrapIn('bold', content) + " "
+        return wrapIn('bold', content) + " "
 
-			case 'pre':
-				var content = st(node.content).split("\n").join(" ;; ")
-				return "`" + content + "`"
+      case 'pre':
+        var content = st(node.content).split("\n").join(" ;; ")
+        return "`" + content + "`"
 
-			case 'list':
-				return node.type + " " + node.content.map(function(node) {
-					return st([ node ])
-				}).join("; ")
+      case 'list':
+        return node.type + " " + node.content.map(function(node) {
+          return st([ node ])
+        }).join("; ")
 
-			case 'list-node':
-				return st(node.content)
+      case 'list-node':
+        return st(node.content)
 
-			case 'hr':
-				return "/// "
+      case 'hr':
+        return "/// "
 
-			case 'table': return ""
+      case 'table': return ""
 
-			// Formatting
-			case 'bold'   : return wrapIn('bold', st(node.content))
-			case 'italic' : return wrapIn('italic', st(node.content))
+      // Formatting
+      case 'bold'   : return wrapIn('bold', st(node.content))
+      case 'italic' : return wrapIn('italic', st(node.content))
 
-			case 'wikilink':
-				var content = node.args[0] || [node.target]
-				  , string  = stringifiers.wikilink.call({st:st}, node,
-						node.target, content)
+      case 'wikilink':
+        var content = node.args[0] || [node.target]
+          , string  = stringifiers.wikilink.call({st:st}, node,
+                                                 node.target, content)
 
-				return string != null ? string : st(content)
+        return string != null ? string : st(content)
 
-			case 'template':
-				return stringifiers.template.call({st:st}, node,
-						node.target, node.args)
+      case 'template':
+        return stringifiers.template.call({st:st}, node,
+            node.target, node.args)
 
-			case 'tag':
-				switch (node.tag) {
-					case 'ref'   : return ""
-					case 'sub'   : return "_" + st(node.content)
-					case 'sup'   : return "^" + st(node.content)
-					case 'small' : return st(node.content)
-					case 'br'    : return " "
+      case 'tag':
+        switch (node.tag) {
+          case 'ref': case 'noinclude':
+            return ""
 
-					default: return "<<tag:" + node.tag + ">>"
-				}
+          case 'sub':       return "_" + st(node.content)
+          case 'sup':       return "^" + st(node.content)
+          case 'small':     return st(node.content)
+          case 'br':        return " "
+          case 'code':      return "`" + st(node.content) + "`"
+          case 'nowiki':    return st([node.raw])
 
-			default:
-				throw new Error("Unimplemented node type: " + node.name)
-		}
-	}
+          default:
+            return "<<tag:" + node.tag + ">>"
+        }
+
+      default:
+        throw new Error("Unimplemented node type: " + node.name)
+    }
+  }
 }

File modules/05-wikipedia/substitutors.js

 // Substitutes stuff like templates and ugly links.
 
+// Removes one "level" of an array, e.g. [[1,2,3], [4,5,6]] => [1,2,3,4,5,6].
+function mconcat(arr) {
+  return Array.prototype.concat.apply([], arr)
+}
+
 //-- Wikipedia ------------------------------------------------------
 exports['wikipedia'] = {
-	template: function(temp, name, args) {
-		var nameL = name.toLowerCase()
+  template: function(temp, name, args) {
+    var nameL = name.toLowerCase()
 
-		// Handle all IPAs equally, regardless of language code
-		if (nameL.slice(0, 3) == 'ipa') {
-			if (args[0] == 'icon') args.shift()
+    // Handle IPA (realised pronounciation)
+    if (nameL.slice(0, 4) == 'ipa-') {
+      if (args[0] == 'icon') args.shift()
 
-			var children = Array.prototype.concat.apply([], args)
-			return this.st([
-				{ name    : 'italic'
-				, content : children
-				} ])
-		}
+      var children = [ "[", args[0].join(" "), "]" ]
 
-		switch (name.toLowerCase()) {
-			case 'nihongo':
-				var content   = args[0]
+      return this.st([ { name:    'italic',
+                         content: children } ])
 
-				if (args[1]) { // args[2] is the japanese word
-					content = content.concat([" ("], args[1])
+    // Handle IPA ("abstract" pronounciation)
+    } else if (nameL.slice(0, 5) == 'ipac-') {
+      var children = [ "/", mconcat(args).join(""), "/" ]
 
-					if (args[2]) { // args[3] is romaji
-						content.push(" ",
-							{ name    : 'italic'
-							, content : args[2]
-							})
-					}
+      return this.st([ { name:    'italic',
+                         content: children } ])
+    }
 
-					if (args[3]) { // args[4] is (literal) translation
-						content = content.concat(", ", args[3])
-					}
+    // Handle other templates
+    switch (nameL) {
+      case 'nihongo':
+        var content   = args[0]
 
-					content.push(")")
-				}
+        if (args[1]) { // args[2] is the japanese word
+          content = content.concat([" ("], args[1])
 
-				return this.st(content)
+          if (args[2]) { // args[3] is romaji
+            content.push(" ",
+              { name    : 'italic'
+              , content : args[2]
+              })
+          }
 
-			default:
-				return ""
-		}
-	},
+          if (args[3]) { // args[4] is (literal) translation
+            content = content.concat(", ", args[3])
+          }
 
-	wikilink: function(link, target, content) {
-		// Hide stuff like images and other crap
-		if (target.match(/^(?:File|Image|Category):/)) {
-			return ""
-		}
+          content.push(")")
+        }
 
-		// Otherwise, fall back to default
-		return null
-	}
+        return this.st(content)
+
+      default:
+        return ""
+    }
+  },
+
+  wikilink: function(link, target, content) {
+    // Hide stuff like images and other crap
+    if (target.match(/^(?:File|Image|Category):/)) {
+      return ""
+    }
+
+    // Otherwise, fall back to default
+    return null
+  }
 }
 
 //-- Wiktionary -----------------------------------------------------
 exports['wiktionary'] = {
-	template: function(temp, name, args) {
-		switch (name) {
-			case 'term':
-				var title   = args[1] && args[1].length ? args[1] : args[0]
-				  , content = []
+  template: function(temp, name, args) {
+    switch (name) {
+      case 'term':
+        var title   = args[1] && args[1].length ? args[1] : args[0]
+          , content = []
 
-				content.push(
-					{ name    : 'wikilink'
-					, target  : args[0]
-					, args    : title
-					})
+        content.push(
+          { name    : 'wikilink'
+          , target  : args[0]
+          , args    : title
+          })
 
-				if (args[2]) { content.push(" (" + args[2] + ")") }
+        if (args[2]) { content.push(" (" + args[2] + ")") }
 
-				return this.st(content)
+        return this.st(content)
 
-			case 'etyl':
-				var code = args[0].join("")
-				return require('./lang-codes')[code] || "<lang:" + code + ">"
+      case 'etyl':
+        var code = args[0].join("")
+        return require('./lang-codes')[code] || "<lang:" + code + ">"
 
-			default:
-				return "<<unknown-template>>"
-		}
-	},
+      default:
+        return "<<unknown-template>>"
+    }
+  },
 
-	wikilink: function() {}
+  wikilink: function() {}
 }
 
 // Load backend
 var backend
 try {
-	backend = require('../backends/node-bindings')
+  backend = require('../backends/node-bindings')
 } catch (err) {
-	console.warn("Couldn't load node backend.")
-	console.warn(err.message, err.stack)
-	backend = null
+  console.warn("Couldn't load node backend.")
+  console.warn(err.message, err.stack)
+  backend = null
 }
 
 exports.backend = backend
 
 /* Core */
 function Bot() {
-	events.EventEmitter.call(this)
-	
-	if (backend == null) {
-		throw new Error("No suitable backend found!")
-	}
-	
-	/* Minimal connector:
-		setup: ->
-		createConnection: ->
-			on: ->
-			connect: ->
-			disconnect: ->
-	*/
+  events.EventEmitter.call(this)
 
-	function nop() {}
-	
-	this.connectors =
-		{ 'irc' : ircConnector
-		, 'www' :
-			{ setup: nop
-			, createConnection: function() {
-					return { on:nop, connect:nop, disconnect:nop } }
-			}
-		}
+  if (backend == null) {
+    throw new Error("No suitable backend found!")
+  }
 
-	this.connections = {}
+  /* Minimal connector:
+    setup: ->
+    createConnection: ->
+      on: ->
+      connect: ->
+      disconnect: ->
+  */
 
-	/*
-	@connectors =
-		'irc': ircConnector
-		'www':
-			setup: ->
-			createConnection: ->
-				on: ->
-				connect: ->
-				disconnect: ->
-	
-	@connections = {}
-	*/
+  function nop() {}
+
+  this.connectors =
+    { 'irc' : ircConnector
+    , 'www' :
+      { setup: nop
+      , createConnection: function() {
+          return { on:nop, connect:nop, disconnect:nop } }
+      }
+    }
+
+  this.connections = {}
+
+  /*
+  @connectors =
+    'irc': ircConnector
+    'www':
+      setup: ->
+      createConnection: ->
+        on: ->
+        connect: ->
+        disconnect: ->
+
+  @connections = {}
+  */
 }
 
 exports.Bot = Bot
 util.inherits(Bot, events.EventEmitter)
 
 Bot.prototype.connect = function(name, protocol, options) {
-	var connector = this.connectors[protocol]
-	
-	if (connector == null) {
-		throw new Error("No connector for protocol '" + protocol + "' found!")
-	}
-	
-	console.log("Opening connection (%s)...", protocol)
-	
-	var bot  = this
-	  , conn = connector.createConnection(options)
+  var connector = this.connectors[protocol]
 
-	conn.connect()
-	conn.on('connect', function() {
-		this.authenticate()
-		bot.emit('connect', this)
-	})
-	conn.on('message', function(msg) {
-		bot.emit('message', this, msg)
-	})
-	
-	this.connections[name] = conn
-	return conn
+  if (connector == null) {
+    throw new Error("No connector for protocol '" + protocol + "' found!")
+  }
+
+  console.log("Opening connection (%s)...", protocol)
+
+  var bot  = this
+    , conn = connector.createConnection(options)
+
+  conn.connect()
+  conn.on('connect', function() {
+    this.authenticate()
+    bot.emit('connect', this)
+  })
+  conn.on('message', function(msg) {
+    bot.emit('message', this, msg)
+  })
+
+  this.connections[name] = conn
+  return conn
 }
 
 Bot.prototype.disconnectAll = function() {
-	for (var name in this.connections) {
-		sys.log("Disconnecting connection %s...", name)
-		this.connections[name].disconnect("Eldis4 shutting down")
-	}
+  for (var name in this.connections) {
+    sys.log("Disconnecting connection %s...", name)
+    this.connections[name].disconnect("Eldis4 shutting down")
+  }
 }
 
 // For now, have quit be an alias for disconnectAll. `quit` might be modified to

File src/connectors/irc.js

 var eldisBackend = null
 
 exports.setup = function(backend) {
-	eldisBackend = backend
+  eldisBackend = backend
 }
 
 //-- Helper functions -----------------------------------------------
 var emit = events.EventEmitter.prototype.emit
 function emitCustom(emitter/*, ...args*/) {
-	var args = Array.prototype.slice.call(arguments, 1)
-	emit.apply(emitter, args)
-	emit.apply(emitter, ['custom'].concat(args))
+  var args = Array.prototype.slice.call(arguments, 1)
+  emit.apply(emitter, args)
+  emit.apply(emitter, ['custom'].concat(args))
 }
 
 function validateChannelName(name) {
-	if (!/^[#&][^,]*$/) {
-		throw new Error("Invalid channel name: '" + name + "'")
-	}
+  if (!/^[#&][^,]*$/) {
+    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)
-	$
+  ^
+  (?: :([^\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 )
+  (       [^!]+ )       #   (nick )
+  (?: ! ( [^@]+ ) )?    # ! (ident)
+  (?: @ (    .+ ) )?    # @ (host )
 /// */
 
 var rawExpression =
-		/^(?::([^ ]+) |)([^:][^ ]*)((?: [^ :][^ ]*)*) (?::(.*)|([^ ]*))$/
+    /^(?::([^ ]+) |)([^:][^ ]*)((?: [^ :][^ ]*)*) (?::(.*)|([^ ]*)[ \t]*)$/
 
   , userExpression = /([^!]+)(?:!([^@]+))?(?:@(.+))?/
 
 
 function parseLine(line) {
-	var matches = line.match(rawExpression)
-	if (!matches) throw new Error("Invalid line: '" + 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]
+  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]
+  // 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}
+    var user = { nick:nick, ident:ident, host:host }
+  } else {
+    var user = { nick:"<server>" }
+  }
 
-	// Parameters
-	var args = [lastArg]
-	if (argsData) {
-		args = argsData.slice(1).split(" ").concat(args)
-	}
-	
-	return (
-		{ nick      : user.nick // (use user.nick)
-		, user      : user
-		, command   : command
-		, arguments : args
-		})
+  user.toString = function() {return this.nick}
+
+  // Parameters
+  var args = [lastArg]
+  if (argsData) {
+    args = argsData.slice(1).split(" ").concat(args)
+  }
+  // console.log(JSON.stringify({ user:user, command:command, args:args }))
+
+  return (
+    { nick      : user.nick // (use user.nick)
+    , user      : user
+    , command   : command
+    , arguments : args
+    })
 }
 
 
 //-- IRCConnection --------------------------------------------------
 function IRCConnection(options) {
-	this.options = options
+  this.options = options
 }
 
 util.inherits(IRCConnection, events.EventEmitter)
 
 IRCConnection.prototype.connect = function() {
-	events.EventEmitter.call(this)
-	
-	var conn   = this
-	  , socket = this.socket = new eldisBackend.BufferedSocket(
-		  { hostname  : this.options.hostname
-		  , port      : this.options.port || 6667
-		  , separator : "\r"
-		  })
-	
-	socket.connect()
-	
-	socket.on('data', function(lineRaw) {
-		var line = parseLine(lineRaw.trimLeft())
-		emitCustom(conn, 'raw', line)
+  events.EventEmitter.call(this)
 
-		switch (line.command.toUpperCase()) {
-			case 'PING':
-				conn.write("PONG", line.arguments[0])
-				console.log("PING %s; replied.", line.arguments)
-				break
+  var conn   = this
+    , socket = this.socket = new eldisBackend.BufferedSocket(
+      { hostname  : this.options.hostname
+      , port      : this.options.port || 6667
+      , separator : "\r"
+      })
 
-			case '376':
-				conn.emit('connected')
-				emitCustom(conn, 'motd')
-				break
+  socket.connect()
 
-			case 'NOTICE':
-				var target  = line.arguments[0]
-				  , message = line.arguments[1]
+  socket.on('data', function(lineRaw) {
+    var line = parseLine(lineRaw.trimLeft())
+    emitCustom(conn, 'raw', line)
 
-				if (target.name == '*') {
-					emitCustom(conn, 'server-notice', message, line.nick)
-				} else {
-					emitCustom(conn, 'notice',
-						{ target  : target
-						, nick    : line.nick
-						, message : message
-						})
-				}
-				break
+    switch (line.command.toUpperCase()) {
+      case 'PING':
+        conn.write("PONG", line.arguments[0])
+        console.log("PING %s; replied.", line.arguments)
+        break
 
-			case 'JOIN':
-				var channel = line.arguments[0]
+      case '376':
+        conn.emit('connected')
+        emitCustom(conn, 'motd')
+        break
 
-				conn.emit('join',
-					{ channel : channel
-					, nick    : line.nick
-					, user    : line.user
-					, hash    : line.user.host.toLowerCase()
-					})
-				break
+      case 'NOTICE':
+        var target  = line.arguments[0]
+          , message = line.arguments[1]
 
-			case 'PRIVMSG':
-				var target  = line.arguments[0]
-				  , message = line.arguments[1]
+        if (target.name == '*') {
+          emitCustom(conn, 'server-notice', message, line.nick)
+        } else {
+          emitCustom(conn, 'notice',
+            { target  : target
+            , nick    : line.nick
+            , message : message
+            })
+        }
+        break
 
-				// Handle queries
-				if (target[0] != '#') {
-					target = line.nick
-				}
-				
-				conn.emit('message',
-					{ target  : target
-					, nick    : line.nick
-					, hash    : line.user.host.toLowerCase()
-					, message : message
-					})
-				break
+      case 'JOIN':
+        var channel = line.arguments[0]
 
-			default:
-				console.log("Unimplemented: %s :: %s",
-						line.command.toUpperCase(), line.arguments.join(" "))
-		}
-	})
+        conn.emit('join',
+          { channel : channel
+          , nick    : line.nick
+          , user    : line.user
+          , hash    : line.user.host.toLowerCase()
+          })
+        break
 
-	socket.on('connect', function() {
-		conn.emit('connect')
-	})
+      case 'PRIVMSG':
+        var target  = line.arguments[0]
+          , message = line.arguments[1]
 
-	socket.on('end', function() {
-		console.log("IRCConnection: Disconnected")
-		conn.emit('disconnect')
-	})
+        // Handle queries
+        if (target[0] != '#') {
+          target = line.nick
+        }
+
+        conn.emit('message',
+          { target  : target
+          , nick    : line.nick
+          , hash    : line.user.host.toLowerCase()
+          , message : message
+          })
+        break
+
+      default:
+        console.log("Unimplemented: %s :: %s",
+            line.command.toUpperCase(), line.arguments.join(" "))
+    }
+  })
+
+  socket.on('connect', function() {
+    conn.emit('connect')
+  })
+
+  socket.on('end', function() {
+    console.log("IRCConnection: Disconnected")
+    conn.emit('disconnect')
+  })
 }
 
 IRCConnection.prototype.authenticate = function() {
-	this.write("NICK", this.options.nickname)
-	this.write("USER", this.options.ident, "-", "-", this.options.realname)
+  this.write("NICK", this.options.nickname)
+  this.write("USER", this.options.ident, "-", "-", this.options.realname)
 }
 
 function isArrayLike(arr) {
-	return arr != null && arr.length != null
+  return arr != null && arr.length != null
 }
 
 /*
 // Needs optimisation!
 IRCConnection.prototype.sendRaw = function(cmd/*, ...args* /) {
-	var args = Array.prototype.slice.call(arguments, 1)
+  var args = Array.prototype.slice.call(arguments, 1)
 
-	console.log(arguments)
-	
-	if (args != null) {
-		if (isArrayLike(args)) {
-			var argsButLast = " " + args.slice(0, args.length-1).join(" ")
-			if (argsButLast == " ") { argsButLast = "" }
+  console.log(arguments)
 
-			this.sendRaw(cmd + argsButLast + " :" + args[args.length-1])
-		} else {
-			this.sendRaw(cmd, args)
-		}
-	
-	} else if (cmd.match(/[\r\n]/)) {
-		throw new Error("Trying to send more than one raw command at "
-				+ "the same time!")
-	} else {
-		console.log("> %s", cmd)
-		this.socket.write(cmd + "\r")
-	}
+  if (args != null) {
+    if (isArrayLike(args)) {
+      var argsButLast = " " + args.slice(0, args.length-1).join(" ")
+      if (argsButLast == " ") { argsButLast = "" }
+
+      this.sendRaw(cmd + argsButLast + " :" + args[args.length-1])
+    } else {
+      this.sendRaw(cmd, args)
+    }
+
+  } else if (cmd.match(/[\r\n]/)) {
+    throw new Error("Trying to send more than one raw command at "
+        + "the same time!")
+  } else {
+    console.log("> %s", cmd)
+    this.socket.write(cmd + "\r")
+  }
 }
 */
 IRCConnection.prototype.write = function(/* command, ...args */) {
-	var args    = Array.prototype.slice.call(arguments)
-	  , lastArg = args.pop()
+  var args    = Array.prototype.slice.call(arguments)
+    , lastArg = args.pop()
 
-	var rawline = args.concat([":" + lastArg]).join(" ")
-	this.sendRaw(rawline)
+  var rawline = args.concat([":" + lastArg]).join(" ")
+  this.sendRaw(rawline)
 }
 IRCConnection.prototype.sendRaw = function(raw) {
-	if (raw.match(/[\r\n]/)) {
-		throw new Error("Trying to send more than one raw command at "
-				+ "the same time!")
-	} else {
-		console.log("> " + raw)
-		this.socket.write(raw + "\r\n")
-	}
+  if (raw.match(/[\r\n]/)) {
+    throw new Error("Trying to send more than one raw command at "
+        + "the same time!")
+  } else {
+    console.log("> " + raw)
+    this.socket.write(raw + "\r\n")
+  }
 }
 
 IRCConnection.prototype.send = function(target, line) {
-	this.write("PRIVMSG", target, line)
+  this.write("PRIVMSG", target, line)
 }
 
 IRCConnection.prototype.action = function(target, line) {
-	this.write("PRIVMSG", target, "\x01ACTION " + line + "\x01")
+  this.write("PRIVMSG", target, "\x01ACTION " + line + "\x01")
 }
 
 IRCConnection.prototype.joinRoom = function(room) {
-	validateChannelName(room)
-	this.write("JOIN", room)
+  validateChannelName(room)
+  this.write("JOIN", room)
 }
 
 IRCConnection.prototype.leaveRoom = function(room) {
-	validateChannelName(room)
-	this.write("PART", room)
+  validateChannelName(room)
+  this.write("PART", room)
 }
 
 IRCConnection.prototype.disconnect = function(reason) {
-	this.write("QUIT", reason)
+  this.write("QUIT", reason)
 }
 
 exports.createConnection = function(options) {
-	return new IRCConnection(options)
+  return new IRCConnection(options)
 }

File src/eldis4.js

 // Shared string formatter
 
 function Eldis4() {
-	bot.Bot.call(this)
-	var self = this
-	
-	this.storage = {}
-	this.config = bot.backend.storage.load('config')
-	this.load()
-	
-	this.triggers = {}
-	
-	function hoistEventToModules(name) {
-		self.on(name, function() {
-			for (var key in self.modules.contexts) {
-				var ctx = self.modules.contexts[key]
-				ctx.module.emit(name)
-			}
-		})
-	}
-	
-	hoistEventToModules('reload')
-	hoistEventToModules('quit')
-	
-	this.autoconnect()
+  bot.Bot.call(this)
+  var self = this
+
+  this.storage = {}
+  this.config = bot.backend.storage.load('config')
+  this.load()
+
+  this.triggers = {}
+
+  function hoistEventToModules(name) {
+    self.on(name, function() {
+      for (var key in self.modules.contexts) {
+        var ctx = self.modules.contexts[key]
+        ctx.module.emit(name)
+      }
+    })
+  }
+
+  hoistEventToModules('reload')
+  hoistEventToModules('quit')
+
+  this.autoconnect()
 }
 
 util.inherits(Eldis4, bot.Bot)
 
 Eldis4.prototype.quit = function() {
-	this.emit('quit')
-	this.modules.unloadAll()
-	bot.Bot.prototype.quit.apply(this, arguments)
+  this.emit('quit')
+  this.modules.unloadAll()
+  bot.Bot.prototype.quit.apply(this, arguments)
 }
 
 Eldis4.prototype.autoconnect = function() {
-	var data        = this.storage.data
-	  , connections = data.connections
-	
-	for (var name in connections) {
-		var conn = connections[name]
-		this.connect(name, conn.protocol, conn.options)
-	}
+  var data        = this.storage.data
+    , connections = data.connections
+
+  for (var name in connections) {
+    var conn = connections[name]
+    this.connect(name, conn.protocol, conn.options)
+  }
 }
 
 Eldis4.prototype.connect = function(name/*, ...args*/) {
-	var self = this
-	  , args = Array.prototype.slice.call(arguments, 1)
-	  , conn = bot.Bot.prototype.connect.apply(this, arguments)
-	
-	conn.on('connected', function() {
-		autoperform = self.storage.data.connections[name].autoperform
-		if (autoperform) {
-			autoperform.call(this)
-		}
-	})
+  var self = this
+    , args = Array.prototype.slice.call(arguments, 1)
+    , conn = bot.Bot.prototype.connect.apply(this, arguments)
 
-	return conn
+  conn.on('connected', function() {
+    autoperform = self.storage.data.connections[name].autoperform
+    if (autoperform) {
+      autoperform.call(this)
+    }
+  })
+
+  return conn
 }
 
 Eldis4.prototype.register = function(name, trigger) {
-	if (!trigger.match || !trigger.call) {
-		throw new Error("Trigger must implement 'call' and 'match'.")
-	}
-	
-	console.log("Registering trigger %s.", name)
-	
-	this.triggers[name] = trigger
+  if (!trigger.match || !trigger.call) {
+    throw new Error("Trigger must implement 'call' and 'match'.")
+  }
 
-	if (trigger.emit) {
-		trigger.emit('init', this)
-	}
+  console.log("Registering trigger %s.", name)
 
-	return trigger
+  this.triggers[name] = trigger
+
+  if (trigger.emit) {
+    trigger.emit('init', this)
+  }
+
+  return trigger
 }
 
 Eldis4.prototype.unregister = function(name) {
-	// Make sure that the trigger we're trying to remove actually exists.
-	if (!this.triggers[name]) {
-		throw new Error("No such trigger found: " + name)
-	}
-	
-	console.log("Unregistering trigger %s.", name)
-	
-	var trigger = this.triggers[name]
-	delete this.triggers[name]
-	
-	if (trigger.emit) {
-		trigger.emit('deinit', this)
-	}
+  // Make sure that the trigger we're trying to remove actually exists.
+  if (!this.triggers[name]) {
+    throw new Error("No such trigger found: " + name)
+  }
 
-	return trigger
+  console.log("Unregistering trigger %s.", name)
+
+  var trigger = this.triggers[name]
+  delete this.triggers[name]
+
+  if (trigger.emit) {
+    trigger.emit('deinit', this)
+  }
+
+  return trigger
 }
 
 Eldis4.prototype.load = function() {
-	this.storage.data = bot.backend.storage.load('data')
+  this.storage.data = bot.backend.storage.load('data')
 }
 
 Eldis4.prototype.save = function() {
-	for (var name in this.storage) {
-		var data = this.storage[name]
-		bot.backend.storage.save(name, data)
-	}
+  for (var name in this.storage) {
+    var data = this.storage[name]
+    bot.backend.storage.save(name, data)
+  }
 }
 
 //-- Rights system --------------------------------------------------
 function accessOperation(op, oper1, oper2) {
-	switch (op) {
-		case '$eq'  : return oper1 == oper2
-		case '$not' : return oper1 != oper2
-		case '$lt'  : return oper1 <  oper2
-		case '$gt'  : return oper1 >  oper2
-		case '$le'  : return oper1 <= oper2
-		case '$ge'  : return oper1 >= oper2
+  switch (op) {
+    case '$eq'  : return oper1 == oper2
+    case '$not' : return oper1 != oper2
+    case '$lt'  : return oper1 <  oper2
+    case '$gt'  : return oper1 >  oper2
+    case '$le'  : return oper1 <= oper2
+    case '$ge'  : return oper1 >= oper2
 
-		default: throw new Error("Invalid operator: " + op)
-	}
+    default: throw new Error("Invalid operator: " + op)
+  }
 }
 
 Eldis4.prototype.hasAccess = function(rights, required) {
-	// rights is e.g. { foo:4 }
-	// required is e.g. { foo: {$ge:4} }
+  // rights is e.g. { foo:4 }
+  // required is e.g. { foo: {$ge:4} }
 
-	for (var key in required) {
-		var req = required[key]
-		if (!req) return false
+  for (var key in required) {
+    var req = required[key]
+    if (!req) return false
 
-		// the left operand is the rights of the user.
-		var oper1 = rights[key]
+    // the left operand is the rights of the user.
+    var oper1 = rights[key]
 
-		// Make sure that *all* constraints set in the requirement object are
-		// met.
-		for (var op in req) {
-			var oper2 = req[op]
-			if (!accessOperation(op, oper1, oper2)) { return false }
-		}
-	}
+    // Make sure that *all* constraints set in the requirement object are
+    // met.
+    for (var op in req) {
+      var oper2 = req[op]
+      if (!accessOperation(op, oper1, oper2)) { return false }
+    }
+  }
 
-	return true
+  return true
 }
 
 Eldis4.prototype.getUserRights = function(connLabel, userHash) {
-	var user = this.storage.data.connections[connLabel].users[userHash]
-	
-	if (user) {
-		return user.rights
-	} else {
-		return {} // default to no rights.
-	}
+  var user = this.storage.data.connections[connLabel].users[userHash]
+
+  if (user) {
+    return user.rights
+  } else {
+    return {} // default to no rights.
+  }
 }
 
 //-- The Eldis4 singleton -------------------------------------------
 var eldis4 = new Eldis4()
 
 eldis4.on('message', function(conn, msg) {
-	// FIXME
-	var connectionLabel = null
-	for (var label in this.connections) {
-		if (conn == this.connections[label]) {
-			connectionLabel = label
-			break
-		}
-	}
+  // FIXME
+  var connectionLabel = null
+  for (var label in this.connections) {
+    if (conn == this.connections[label]) {
+      connectionLabel = label
+      break
+    }
+  }
 
-	var ctx = { label:connectionLabel }
-	
-	for (var key in this.triggers) {
-		var trig    = this.triggers[key]
-		  , isMatch = false
-		
-		if (trig.match.constructor.name == 'RegExp') {
-			isMatch = trig.match.exec(msg.message)
-		} else if (typeof trig.match == 'function') {
-			isMatch = trig.match(msg.message)
-		}
+  var ctx = { label:connectionLabel }
 
-		if (isMatch) {
-			trig.call.call(ctx, conn, msg)
-			break
-		}
-	}
+  for (var key in this.triggers) {
+    var trig    = this.triggers[key]
+      , isMatch = false
+
+    if (trig.match.constructor.name == 'RegExp') {
+      isMatch = trig.match.exec(msg.message)
+    } else if (typeof trig.match == 'function') {
+      isMatch = trig.match(msg.message)
+    }
+
+    if (isMatch) {
+      trig.call.call(ctx, conn, msg)
+      break
+    }
+  }
 })
 
 //-- The module system ----------------------------------------------
 eldis4.modules.contexts = {}
 
 eldis4.modules.load = function(name, asDir) {
-	// Set the filename to load, depending on whether we're loading a directory
-	// module or a singe-file module.
-	if (asDir) {
-		var filename = "modules/" + name + "/index.js"
-	} else {
-		var filename = "modules/" + name + ".js"
-	}
+  // Set the filename to load, depending on whether we're loading a directory
+  // module or a singe-file module.
+  if (asDir) {
+    var filename = "modules/" + name + "/index.js"
+  } else {
+    var filename = "modules/" + name + ".js"
+  }
 
-	// Fetch/create module storage.
-	var eldisData = eldis4.storage.data
-	eldisData.moduleData || (eldisData.moduleData = {})
+  // Fetch/create module storage.
+  var eldisData = eldis4.storage.data
+  eldisData.moduleData || (eldisData.moduleData = {})
 
-	var moduleData      = eldisData.moduleData
-	  , specificStorage = moduleData[name] || (moduleData[name] = {})
-	
-	// Load the module file.
-	bot.backend.readFile(filename, function(err, data) {
-		if (err) throw err
+  var moduleData      = eldisData.moduleData
+    , specificStorage = moduleData[name] || (moduleData[name] = {})
 
-		var moduleObj = new events.EventEmitter()
-		moduleObj["name"]        = name
-		moduleObj["storage"]     = specificStorage
-		moduleObj["meta"]        = {}
-		moduleObj["isDirectory"] = asDir
+  // Load the module file.
+  bot.backend.readFile(filename, function(err, data) {
+    if (err) throw err
 
-		// Writing to `storageDefaults` sets the default storage values. Useful
-		// for initialising e.g. configuration.
-		Object.defineProperty(moduleObj, 'storageDefaults', {
-			get: function() {
-				throw new Error("Don't read storageDefaults.")
-			},
-			set: function(obj) {
-				for (var key in obj) {
-					// Copy all nonexistent storage values.
-					if (key in specificStorage) continue
-					specificStorage[key] = value
-				}
-			}
-		})
+    var moduleObj = new events.EventEmitter()
+    moduleObj["name"]        = name
+    moduleObj["storage"]     = specificStorage
+    moduleObj["meta"]        = {}
+    moduleObj["isDirectory"] = asDir
 
-		// Creates a function that modules can use to log stuff.
-		moduleObj["log"] = function() {
-			Array.prototype.forEach.call(arguments, function(line) {
-				console.log("[%s]: %s", name, line)
-			})
-		}
-		
-		// The actual context that the module will be run in.
-		var ctx =
-			{ require    : require
-			, console    : console
-			, setTimeout : setTimeout
-			
-			, bot        : eldis4
-			, module     : moduleObj
-			, backend    : bot.backend
-			}
-		
-		eldis4.modules.contexts[name] = ctx
-		
-		// Time to actually run it!
-		try {
-			vm.runInNewContext(data.toString(), ctx, name)
+    // Writing to `storageDefaults` sets the default storage values. Useful
+    // for initialising e.g. configuration.