// shell.js
//

var npm = require('npm'),
    path = require('path'),
    vm = require('vm');

var _knownModules = {
  crypto: 'crypto',
  events: 'events',
  fs: 'fs',
  http: 'http',
  https: 'https',
  net: 'net',
  os: 'os',
  path: 'path',
  stream: 'stream',
  querystring: 'querystring',
  url: 'url',
  util: 'util',
  zlib: 'zlib'
};

var _commandPattern = /^%%?([a-zA-Z0-9\\._]+)(\s+)?([^\n]*)?(\n)?(.*)?$/;

var _config;
var _loadedModules = {};

function shellRequire(name) {
  var module = _loadedModules[name];
  if (module) {
    return module;
  }

  if (_knownModules[name]) {
    module = require(name);
  }
  else {
    var modulePath = path.join(_config.modulesPath, 'node_modules', name);
    module = require(modulePath);
  }

  if (module) {
    _loadedModules[name] = module;
  }

  return module;
};

var _state = {
  Buffer: Buffer,
  console: console,
  global: _state,
  require: shellRequire
};
var _context = vm.createContext(_state);

function evaluate(text, evaluationId) {
  if (text.charAt(0) === '%') {
    return evaluateCommand(text, evaluationId);
  }
  else {
    return evaluateCode(text, evaluationId);
  }
}

function evaluateCode(code, evaluationId) {
  return vm.runInContext(code, _context, 'code');
}

function evaluateCommand(text, evaluationId) {
  var match = _commandPattern.exec(text);
  if (!match) {
    // TODO: Custom error type
    throw new Error('Invalid command syntax.');
  }

  var commandName = match[1];
  var commandArgs = match[3].trim().split(' ');
  var commandData = match[5];

  // TODO: Generalize
  if (commandName == 'module') {
    if (commandArgs.length != 1) {
      throw new Error('Expected module name argument');
    }

    npm.commands.install(_config.modulesPath, commandArgs, function() { });
  }
}

function createShell(config, callback) {
  _config = config;

  var shell = {
    evaluate: evaluate
  };

  npm.load(function() {
    callback(shell);
  });
}

module.exports = {
  create: createShell
};

@@ -416,517 +416,312 @@
 /;%0A%0A
-var _config;%0Avar _loadedModules = %7B%7D;%0A%0Afunction shellRequire(name) %7B%0A  var module = _loadedModules%5Bname%5D;%0A  if (module) %7B%0A    return module;%0A  %7D%0A%0A  if (_knownModules%5Bname%5D) %7B%0A    module = require(name);%0A  %7D%0A  else %7B%0A    var modulePath = path.join(_config.modulesPath, 'node_modules', name);%0A    module = require(modulePath);%0A  %7D%0A%0A  if (module) %7B%0A    _loadedModules%5Bname%5D = module;%0A  %7D%0A%0A  return module
+function createGlobals(shell) %7B%0A  var globals = %7B%0A    Buffer: Buffer,%0A    global: globals,%0A    console: console,%0A    require: function(name) %7B%0A      return shell._require(name);%0A    %7D%0A  %7D;%0A  return globals
 ;%0A%7D
-;
 %0A%0A
-var _state = %7B%0A  Buffer: Buffer,%0A  console: console,%0A  global: _state,%0A  require: shellRequire%0A%7D;%0Avar 
+function Shell(config) %7B%0A  this._config = config;%0A  this._state = createGlobals(this);%0A  this.
 _con
@@ -744,16 +744,21 @@
 Context(
+this.
 _state);
@@ -758,34 +758,112 @@
 state);%0A
-%0Afunction evaluate
+  this._requiredModules = %7B%7D;%0A  this._loadedModules = %7B%7D;%0A%7D%0A%0AShell.prototype.evaluate = function
 (text, e
@@ -912,32 +912,38 @@
 ') %7B%0A    return 
+this._
 evaluateCommand(
@@ -987,16 +987,22 @@
  return 
+this._
 evaluate
@@ -1030,33 +1030,41 @@
 Id);%0A  %7D%0A%7D%0A%0A
-function 
+Shell.prototype._
 evaluateCode
@@ -1063,16 +1063,27 @@
 uateCode
+ = function
 (code, e
@@ -1128,16 +1128,21 @@
 t(code, 
+this.
 _context
@@ -1155,25 +1155,33 @@
 e');%0A%7D%0A%0A
-function 
+Shell.prototype._
 evaluate
@@ -1179,32 +1179,43 @@
 _evaluateCommand
+ = function
 (text, evaluatio
@@ -1625,16 +1625,38 @@
 %0A    %7D%0A%0A
+    var shell = this;%0A
     npm.
@@ -1672,16 +1672,22 @@
 install(
+shell.
 _config.
@@ -1728,126 +1728,517 @@
 () %7B
- %7D);%0A  %7D%0A%7D%0A%0Afunction createShell(config, callback) %7B%0A  _config = config;%0A%0A  var shell = %7B%0A    evaluate: evaluate%0A  %7D;%0A
+%0A      shell._loadedModules%5BcommandArgs%5B0%5D%5D = true;%0A    %7D);%0A  %7D%0A%7D%0A%0AShell.prototype._require = function(name) %7B%0A  var module = this._requiredModules%5Bname%5D;%0A  if (module) %7B%0A    return module;%0A  %7D%0A%0A  if (_knownModules%5Bname%5D) %7B%0A    module = require(name);%0A  %7D%0A  else %7B%0A    var modulePath = path.join(this._config.modulesPath, 'node_modules', name);%0A    module = require(modulePath);%0A  %7D%0A%0A  if (module) %7B%0A    this._requiredModules%5Bname%5D = module;%0A  %7D%0A%0A  return module;%0A%7D;%0A%0A%0Afunction createShell(config, callback) %7B
 %0A  n
@@ -2271,21 +2271,33 @@
 allback(
-shell
+new Shell(config)
 );%0A  %7D);

Reorganize shell code