<commit_before>/**
 * Shumway Build Script
 *
 * This script performs several source level transformations:
 * - Expands "load(file);" expression statements.
 * - Removes assertion statements.
 * - Replaces identifiers. This useful for constant folding, e.g.
 *   |$DEBUG ? A : B| can be optimized away to |B| by the closure compiler
 *   if the identifier $DEBUG is replaced with |false|.
 * - Optionally runs the closure compiler.
 */

var path = require('path');
var fs   = require('fs');
var readFile = fs.readFileSync;
var spawn = require('child_process').spawn;

global.assert = function () { };
global.release = false;

var shumwayRoot = __dirname + "/../../";
var amv2Root = shumwayRoot + "src/avm2/";

var options = require(amv2Root + "options.js");

var ArgumentParser = options.ArgumentParser;
var Option = options.Option;
var OptionSet = options.OptionSet;

var esprima = require(amv2Root + "compiler/lljs/src/esprima.js");
var escodegen = require(amv2Root + "compiler/lljs/src/escodegen.js");
var estransform = require(amv2Root + "compiler/lljs/src/estransform.js");

// Import nodes
var T = estransform;
var Node = T.Node;
var Literal = T.Literal;
var Identifier = T.Identifier;
var CallExpression = T.CallExpression;
var ExpressionStatement = T.ExpressionStatement;
var FunctionExpression = T.FunctionExpression;
var FunctionDeclaration = T.FunctionDeclaration;
var MemberExpression = T.MemberExpression;
var AssignmentExpression = T.AssignmentExpression;
var BlockStatement = T.BlockStatement;
var VariableDeclaration = T.VariableDeclaration;
var VariableDeclarator = T.VariableDeclarator;
var ArrayExpression = T.ArrayExpression;
var BinaryExpression = T.BinaryExpression;
var LogicalExpression = T.LogicalExpression;
var ObjectExpression = T.ObjectExpression;
var Property = T.Property;

// Parse arguments
var arguments = process.argv.slice(2);
var argumentParser = new ArgumentParser();
var buildOptions = new OptionSet("Build Options");

var closure = buildOptions.register(new Option("c", "closure", "string", "", "runs the closure compiler"));
var instrument = buildOptions.register(new Option("ic", "instrument", "boolean", false, "instruments functions"));
var profileLoad = buildOptions.register(new Option("pl", "profileLoad", "boolean", false, "profile load statements"));
var foldConstants = buildOptions.register(new Option("fc", "foldConstants", "boolean", false, "folds constants of the form XXX_..."));

var debug = buildOptions.register(new Option("d", "debug", "boolean", false, "debug mode"));

argumentParser.addBoundOptionSet(buildOptions);

argumentParser.addArgument("h", "help", "boolean", {parse: function (x) {
  console.log("build.js " + argumentParser.getUsage());
  process.exit();
}});

var file = argumentParser.addArgument("file", "file", "string", {
  positional: true
});

try {
  argumentParser.parse(arguments);
} catch (x) {
  console.log(x.message);
  process.exit();
}

if (!file.value) {
  process.exit();
}

function listJSFiles(dir, prefix, output) {
  var files = fs.readdirSync(dir);
  for (var i = 0; i < files.length; i++) {
    var file = files[i];
    var s = fs.statSync(path.join(dir, file));
    if (s.isDirectory()) {
      listJSFiles(path.join(dir, file), prefix + file + '/', output);
    } else if (s.isFile() && /\.js$/i.test(file)) {
      output.push(prefix + file);
    }
  }
}

Node.prototype.transform = T.makePass("transform", "transformNode");

CallExpression.prototype.transform = function (o) {
  this.callee = this.callee.transform(o);
  if (this.callee instanceof Identifier) {
    // Remove assertions.
    if (this.callee.name === "assert") {
      return new Literal(true);
    } else if (this.callee.name === "loadJSON") {
      var path = this.arguments[0].transform(o).value;
      var node = esprima.parse("(" + readFile(path) + ")");
      node = T.lift(node);
      node = node.transform(o);
      return node.body[0].expression;
    }
  }
  this.arguments = this.arguments.map(function (x) {
    return x.transform(o);
  });
  return this;
};

Identifier.prototype.transform = function(o) {
  if (!o.inVariableDeclaration && !o.inAssignment &&
      o.constants && o.constants.hasOwnProperty(this.name)) {
    return o.constants[this.name];
  }
  return this;
};

function wrapBlockWithProfilingStatements(name, block) {
  name = path.normalize(name);
  name = "..." + name.substring(name.length - 24);
  var body = [];
  body.push(esprima.parse("console.time(\"" + name + "\");"));
  body.push(block);
  body.push(esprima.parse("console.timeEnd(\"" + name + "\");"));
  return new BlockStatement(body);
}

ExpressionStatement.prototype.transform = function (o) {
  if (this.expression instanceof CallExpression &&
      this.expression.callee.name === "assert") {
    return null;
  }
  this.expression = this.expression.transform(o);
  if (this.expression === null) {
    return null;
  }
  // Expand |load(file)|.
  if (this.expression instanceof CallExpression &&
      this.expression.callee.name === "load" &&
      this.expression.arguments.length === 1 &&
      this.expression.arguments[0] instanceof Literal) {
    var path = this.expression.arguments[0].value;
    if (path[0] !== "/" && path[1] !== ':') {
      path = __dirname + "/" + path;
    }
    if(fs.statSync(path).isDirectory()) {
      var list = [];
      listJSFiles(path, '', list);
      list.sort();
      var block =  new BlockStatement(list.map(function (file) {
        var node = esprima.parse(readFile(path + "/" + file));
        node = T.lift(node);
        node = node.transform(o);
        return new BlockStatement(node.body);
      }));
      if (profileLoad.value) {
        block = wrapBlockWithProfilingStatements(path, block);
      }
      return block;
    } else {
      var node = esprima.parse(readFile(path));
      node = T.lift(node);
      node = node.transform(o);
      var block = new BlockStatement(node.body);
      if (profileLoad.value) {
        block = wrapBlockWithProfilingStatements(path, block);
      }
      return block;
    }
  } else if (this.expression instanceof Literal &&
             this.expression.value === "use strict") {
    return null;
  }
  return this;
};

BinaryExpression.prototype.transform = function (o) {
  this.left = this.left.transform(o);
  this.right = this.right.transform(o);
  if (this.operator === "+" && this.left instanceof Literal &&
      this.right instanceof Literal) {
    return new Literal(this.left.value + this.right.value);
  } else if (this.operator === "||" &&
             this.left instanceof Literal &&
             this.left.value === true) {
    return this.left;
  }
  return this;
};

LogicalExpression.prototype.transform = function (o) {
  this.left = this.left.transform(o);
  this.right = this.right.transform(o);
  if (this.operator === "||" && this.left instanceof Literal &&
      this.left.value === true) {
    return this.left;
  }
  return this;
};

function isUpperCase(s) {
  return s === s.toUpperCase();
}

function isConstantIdentifier(name) {
  if (name.length > 0 && name[0] === "$") {
    return false;
  }
  var i = name.indexOf("_");
  if (i > 0) {
    return isUpperCase(name.substr(0, i));
  }
  return isUpperCase(name);
}

VariableDeclarator.prototype.transform = function (o) {
  this.id = this.id.transform(Object.create({inVariableDeclarator: true}, o));
  if (this.init) {
    this.init = this.init.transform(o);
    if (foldConstants.value && isConstantIdentifier(this.id.name) && this.init instanceof Literal) {
      if (this.id.name in constants && this.init.value !== constants[this.id.name].value) {
        throw "Constant " + this.id.name + " already defined as " + constants[this.id.name].value + " now redefining as " + this.init.value;
      }
      constants[this.id.name] = this.init;
    }
  }
  return this;
};

AssignmentExpression.prototype.transform = function (o) {
  this.left = this.left.transform(Object.create({inAssignment: true}, o));
  this.right = this.right.transform(o);
  return this;
};

Property.prototype.transform = function (o) {
  // this.key = NOP
  this.value = this.value.transform(o);
  return this;
};

MemberExpression.prototype.transform = function (o) {
  if (this.computed) {
    this.property = this.property.transform(o);
  }
  return this;
};

var functionCounterMap = {};

if (instrument.value) {
  FunctionExpression.prototype.transform = FunctionDeclaration.prototype.transform = function (o) {
    o.scopePath.push(o.scopePath.pop() + 1);
    var functionId = "FN_" + o.scopePath.join("_");
    functionCounterMap[functionId] = this.id ? this.id.name : functionId;
    var a = new AssignmentExpression(new Identifier(functionId), "+=", new Literal(1));
    o.scopePath.push(0);
    this.body.transform(o);
    o.scopePath.pop();
    this.body.body.unshift(new ExpressionStatement(a));
    return this;
  };
}

var source = readFile(file.value).toString();
var node = esprima.parse(source, { loc: true, comment: true, range: true, tokens: true });
node = T.lift(node);

var constants = {
  // "$X": new Literal(true)
};

process.env["SHUMWAY_ROOT"] = shumwayRoot;

// Make environment variables available.
for (var key in process.env) {
  constants["$" + key] = new Literal(process.env[key]);
}

constants["$DEBUG"] = new Literal(debug.value);
constants["$RELEASE"] = new Literal(!debug.value);

node = node.transform({constants: constants, scopePath: [0]});

// Declare function counters.
if (Object.keys(functionCounterMap).length) {
  // Declare counters.
  node.body.unshift(new BlockStatement([
    new VariableDeclaration("var", Object.keys(functionCounterMap).map(function (x) {
      return new VariableDeclarator(new Identifier(x), new Literal(0));
    }))
  ]));

  // Collect counters in an object expression.
  node.body.push(new BlockStatement([
    new VariableDeclaration("var", [
      new VariableDeclarator(new Identifier("FUNCTION_COUNTERS"),
        new ObjectExpression(
          Object.keys(functionCounterMap).map(function (x) {
            return new Property(new Literal(functionCounterMap[x]), new Identifier(x), "init");
          })
        )
      )
    ])
  ]));

  // Print out counters at the end.
  node.body.push(new BlockStatement(esprima.parse('print(Object.keys(FUNCTION_COUNTERS).map(function (x) {' +
    'return {name: x, value: FUNCTION_COUNTERS[x]};})' +
    '.sort(function (a, b) { return a.value - b.value; })' +
    '.filter(function (x) { return x.value > 1000; })' +
    '.map(function (x) { return x.name + ": " + x.value; })' +
    '.join("\\n"))').body));
}

for (var i = 0; i < node.body.length; i++) {
  if (node.body[i] instanceof BlockStatement) {
    Array.prototype.splice.apply(node.body, [i, 1].concat(node.body[i].body));
  }
}

var code = escodegen.generate(node, { base: "", indent: "  ", comment: false });

if (closure.value) {
  if (!("CLOSURE" in process.env)) {
    console.log("Set the |CLOSURE| environment variable to the Closure-Compiler compiler.jar file");
    process.exit();
  }
  var closureOptions = ["-jar", process.env["CLOSURE"]];
  closureOptions.push("--accept_const_keyword");

  // There's some bug in Closure that gets triggered if we don't use pretty printing.
  closureOptions.push("--formatting");
  closureOptions.push("PRETTY_PRINT");

  closureOptions.push("--language_in");
  closureOptions.push("ECMASCRIPT5");
  closureOptions.push("--compilation_level");
  var optimizations = {a: "ADVANCED_OPTIMIZATIONS", s: "SIMPLE_OPTIMIZATIONS", w: "WHITESPACE_ONLY"};
  if (!(closure.value in optimizations)) {
    console.log("Unknown optimization level: " + closure.value + ", must be one of:");
    console.log(optimizations);
    process.exit();
  }
  closureOptions.push(optimizations[closure.value]);
  var cc = spawn("java", closureOptions);
  cc.stdout.on('data', function (data) {
    process.stdout.write(data);
  });
  cc.stdin.write(code);
  cc.stdin.end();
} else {
  console.log(code);
}
<commit_msg>Perform transform on MemberExpression's object in build.js<commit_after>/**
 * Shumway Build Script
 *
 * This script performs several source level transformations:
 * - Expands "load(file);" expression statements.
 * - Removes assertion statements.
 * - Replaces identifiers. This useful for constant folding, e.g.
 *   |$DEBUG ? A : B| can be optimized away to |B| by the closure compiler
 *   if the identifier $DEBUG is replaced with |false|.
 * - Optionally runs the closure compiler.
 */

var path = require('path');
var fs   = require('fs');
var readFile = fs.readFileSync;
var spawn = require('child_process').spawn;

global.assert = function () { };
global.release = false;

var shumwayRoot = __dirname + "/../../";
var amv2Root = shumwayRoot + "src/avm2/";

var options = require(amv2Root + "options.js");

var ArgumentParser = options.ArgumentParser;
var Option = options.Option;
var OptionSet = options.OptionSet;

var esprima = require(amv2Root + "compiler/lljs/src/esprima.js");
var escodegen = require(amv2Root + "compiler/lljs/src/escodegen.js");
var estransform = require(amv2Root + "compiler/lljs/src/estransform.js");

// Import nodes
var T = estransform;
var Node = T.Node;
var Literal = T.Literal;
var Identifier = T.Identifier;
var CallExpression = T.CallExpression;
var ExpressionStatement = T.ExpressionStatement;
var FunctionExpression = T.FunctionExpression;
var FunctionDeclaration = T.FunctionDeclaration;
var MemberExpression = T.MemberExpression;
var AssignmentExpression = T.AssignmentExpression;
var BlockStatement = T.BlockStatement;
var VariableDeclaration = T.VariableDeclaration;
var VariableDeclarator = T.VariableDeclarator;
var ArrayExpression = T.ArrayExpression;
var BinaryExpression = T.BinaryExpression;
var LogicalExpression = T.LogicalExpression;
var ObjectExpression = T.ObjectExpression;
var Property = T.Property;

// Parse arguments
var arguments = process.argv.slice(2);
var argumentParser = new ArgumentParser();
var buildOptions = new OptionSet("Build Options");

var closure = buildOptions.register(new Option("c", "closure", "string", "", "runs the closure compiler"));
var instrument = buildOptions.register(new Option("ic", "instrument", "boolean", false, "instruments functions"));
var profileLoad = buildOptions.register(new Option("pl", "profileLoad", "boolean", false, "profile load statements"));
var foldConstants = buildOptions.register(new Option("fc", "foldConstants", "boolean", false, "folds constants of the form XXX_..."));

var debug = buildOptions.register(new Option("d", "debug", "boolean", false, "debug mode"));

argumentParser.addBoundOptionSet(buildOptions);

argumentParser.addArgument("h", "help", "boolean", {parse: function (x) {
  console.log("build.js " + argumentParser.getUsage());
  process.exit();
}});

var file = argumentParser.addArgument("file", "file", "string", {
  positional: true
});

try {
  argumentParser.parse(arguments);
} catch (x) {
  console.log(x.message);
  process.exit();
}

if (!file.value) {
  process.exit();
}

function listJSFiles(dir, prefix, output) {
  var files = fs.readdirSync(dir);
  for (var i = 0; i < files.length; i++) {
    var file = files[i];
    var s = fs.statSync(path.join(dir, file));
    if (s.isDirectory()) {
      listJSFiles(path.join(dir, file), prefix + file + '/', output);
    } else if (s.isFile() && /\.js$/i.test(file)) {
      output.push(prefix + file);
    }
  }
}

Node.prototype.transform = T.makePass("transform", "transformNode");

CallExpression.prototype.transform = function (o) {
  this.callee = this.callee.transform(o);
  if (this.callee instanceof Identifier) {
    // Remove assertions.
    if (this.callee.name === "assert") {
      return new Literal(true);
    } else if (this.callee.name === "loadJSON") {
      var path = this.arguments[0].transform(o).value;
      var node = esprima.parse("(" + readFile(path) + ")");
      node = T.lift(node);
      node = node.transform(o);
      return node.body[0].expression;
    }
  }
  this.arguments = this.arguments.map(function (x) {
    return x.transform(o);
  });
  return this;
};

Identifier.prototype.transform = function(o) {
  if (!o.inVariableDeclaration && !o.inAssignment &&
      o.constants && o.constants.hasOwnProperty(this.name)) {
    return o.constants[this.name];
  }
  return this;
};

function wrapBlockWithProfilingStatements(name, block) {
  name = path.normalize(name);
  name = "..." + name.substring(name.length - 24);
  var body = [];
  body.push(esprima.parse("console.time(\"" + name + "\");"));
  body.push(block);
  body.push(esprima.parse("console.timeEnd(\"" + name + "\");"));
  return new BlockStatement(body);
}

ExpressionStatement.prototype.transform = function (o) {
  if (this.expression instanceof CallExpression &&
      this.expression.callee.name === "assert") {
    return null;
  }
  this.expression = this.expression.transform(o);
  if (this.expression === null) {
    return null;
  }
  // Expand |load(file)|.
  if (this.expression instanceof CallExpression &&
      this.expression.callee.name === "load" &&
      this.expression.arguments.length === 1 &&
      this.expression.arguments[0] instanceof Literal) {
    var path = this.expression.arguments[0].value;
    if (path[0] !== "/" && path[1] !== ':') {
      path = __dirname + "/" + path;
    }
    if(fs.statSync(path).isDirectory()) {
      var list = [];
      listJSFiles(path, '', list);
      list.sort();
      var block =  new BlockStatement(list.map(function (file) {
        var node = esprima.parse(readFile(path + "/" + file));
        node = T.lift(node);
        node = node.transform(o);
        return new BlockStatement(node.body);
      }));
      if (profileLoad.value) {
        block = wrapBlockWithProfilingStatements(path, block);
      }
      return block;
    } else {
      var node = esprima.parse(readFile(path));
      node = T.lift(node);
      node = node.transform(o);
      var block = new BlockStatement(node.body);
      if (profileLoad.value) {
        block = wrapBlockWithProfilingStatements(path, block);
      }
      return block;
    }
  } else if (this.expression instanceof Literal &&
             this.expression.value === "use strict") {
    return null;
  }
  return this;
};

BinaryExpression.prototype.transform = function (o) {
  this.left = this.left.transform(o);
  this.right = this.right.transform(o);
  if (this.operator === "+" && this.left instanceof Literal &&
      this.right instanceof Literal) {
    return new Literal(this.left.value + this.right.value);
  } else if (this.operator === "||" &&
             this.left instanceof Literal &&
             this.left.value === true) {
    return this.left;
  }
  return this;
};

LogicalExpression.prototype.transform = function (o) {
  this.left = this.left.transform(o);
  this.right = this.right.transform(o);
  if (this.operator === "||" && this.left instanceof Literal &&
      this.left.value === true) {
    return this.left;
  }
  return this;
};

function isUpperCase(s) {
  return s === s.toUpperCase();
}

function isConstantIdentifier(name) {
  if (name.length > 0 && name[0] === "$") {
    return false;
  }
  var i = name.indexOf("_");
  if (i > 0) {
    return isUpperCase(name.substr(0, i));
  }
  return isUpperCase(name);
}

VariableDeclarator.prototype.transform = function (o) {
  this.id = this.id.transform(Object.create({inVariableDeclarator: true}, o));
  if (this.init) {
    this.init = this.init.transform(o);
    if (foldConstants.value && isConstantIdentifier(this.id.name) && this.init instanceof Literal) {
      if (this.id.name in constants && this.init.value !== constants[this.id.name].value) {
        throw "Constant " + this.id.name + " already defined as " + constants[this.id.name].value + " now redefining as " + this.init.value;
      }
      constants[this.id.name] = this.init;
    }
  }
  return this;
};

AssignmentExpression.prototype.transform = function (o) {
  this.left = this.left.transform(Object.create({inAssignment: true}, o));
  this.right = this.right.transform(o);
  return this;
};

Property.prototype.transform = function (o) {
  // this.key = NOP
  this.value = this.value.transform(o);
  return this;
};

MemberExpression.prototype.transform = function (o) {
  this.object = this.object.transform(o);
  if (this.computed) {
    this.property = this.property.transform(o);
  }
  return this;
};

var functionCounterMap = {};

if (instrument.value) {
  FunctionExpression.prototype.transform = FunctionDeclaration.prototype.transform = function (o) {
    o.scopePath.push(o.scopePath.pop() + 1);
    var functionId = "FN_" + o.scopePath.join("_");
    functionCounterMap[functionId] = this.id ? this.id.name : functionId;
    var a = new AssignmentExpression(new Identifier(functionId), "+=", new Literal(1));
    o.scopePath.push(0);
    this.body.transform(o);
    o.scopePath.pop();
    this.body.body.unshift(new ExpressionStatement(a));
    return this;
  };
}

var source = readFile(file.value).toString();
var node = esprima.parse(source, { loc: true, comment: true, range: true, tokens: true });
node = T.lift(node);

var constants = {
  // "$X": new Literal(true)
};

process.env["SHUMWAY_ROOT"] = shumwayRoot;

// Make environment variables available.
for (var key in process.env) {
  constants["$" + key] = new Literal(process.env[key]);
}

constants["$DEBUG"] = new Literal(debug.value);
constants["$RELEASE"] = new Literal(!debug.value);

node = node.transform({constants: constants, scopePath: [0]});

// Declare function counters.
if (Object.keys(functionCounterMap).length) {
  // Declare counters.
  node.body.unshift(new BlockStatement([
    new VariableDeclaration("var", Object.keys(functionCounterMap).map(function (x) {
      return new VariableDeclarator(new Identifier(x), new Literal(0));
    }))
  ]));

  // Collect counters in an object expression.
  node.body.push(new BlockStatement([
    new VariableDeclaration("var", [
      new VariableDeclarator(new Identifier("FUNCTION_COUNTERS"),
        new ObjectExpression(
          Object.keys(functionCounterMap).map(function (x) {
            return new Property(new Literal(functionCounterMap[x]), new Identifier(x), "init");
          })
        )
      )
    ])
  ]));

  // Print out counters at the end.
  node.body.push(new BlockStatement(esprima.parse('print(Object.keys(FUNCTION_COUNTERS).map(function (x) {' +
    'return {name: x, value: FUNCTION_COUNTERS[x]};})' +
    '.sort(function (a, b) { return a.value - b.value; })' +
    '.filter(function (x) { return x.value > 1000; })' +
    '.map(function (x) { return x.name + ": " + x.value; })' +
    '.join("\\n"))').body));
}

for (var i = 0; i < node.body.length; i++) {
  if (node.body[i] instanceof BlockStatement) {
    Array.prototype.splice.apply(node.body, [i, 1].concat(node.body[i].body));
  }
}

var code = escodegen.generate(node, { base: "", indent: "  ", comment: false });

if (closure.value) {
  if (!("CLOSURE" in process.env)) {
    console.log("Set the |CLOSURE| environment variable to the Closure-Compiler compiler.jar file");
    process.exit();
  }
  var closureOptions = ["-jar", process.env["CLOSURE"]];
  closureOptions.push("--accept_const_keyword");

  // There's some bug in Closure that gets triggered if we don't use pretty printing.
  closureOptions.push("--formatting");
  closureOptions.push("PRETTY_PRINT");

  closureOptions.push("--language_in");
  closureOptions.push("ECMASCRIPT5");
  closureOptions.push("--compilation_level");
  var optimizations = {a: "ADVANCED_OPTIMIZATIONS", s: "SIMPLE_OPTIMIZATIONS", w: "WHITESPACE_ONLY"};
  if (!(closure.value in optimizations)) {
    console.log("Unknown optimization level: " + closure.value + ", must be one of:");
    console.log(optimizations);
    process.exit();
  }
  closureOptions.push(optimizations[closure.value]);
  var cc = spawn("java", closureOptions);
  cc.stdout.on('data', function (data) {
    process.stdout.write(data);
  });
  cc.stdin.write(code);
  cc.stdin.end();
} else {
  console.log(code);
}
<|endoftext|>


<commit_before>(function($) {

    var $input,
        $body = $( 'body' ).first(),

        enter = $.Event( 'keydown' );

    enter.which = 13;

    function _test( str, fn ) {

        // setup
        $input = $( '<input/>' ).appendTo($body);

        test( str, fn );

        // teardown
        $input.remove();
    }

    _test( 'it should return itself', function() {

        equal( $input.historize(), $input );

    });

    _test( 'it should have an empty history at the beginning', function() {

        $input.historize();

        notStrictEqual( $input.data( 'historize.id' ), undefined );
        strictEqual( $input.data( 'historize.index' ), null );

    });

    _test( 'it should not extend the history on "Enter" if empty', function() {

        $input.historize().trigger( enter );

        strictEqual( $input.data( 'historize.index' ), null );

        deepEqual( $input.historize( 'get' ), [] );

    });

})(jQuery);
<commit_msg>one test added.<commit_after>(function($) {

    var $input,
        $body = $( 'body' ).first(),

        enter = $.Event( 'keydown' );

    enter.which = 13;

    function _test( str, fn ) {

        // setup
        $input = $( '<input/>' ).appendTo($body);

        test( str, fn );

        // teardown
        $input.remove();
    }

    _test( 'it should return itself', function() {

        equal( $input.historize(), $input );

    });

    _test( 'it should have an empty history at the beginning', function() {

        $input.historize();

        notStrictEqual( $input.data( 'historize.id' ), undefined );
        strictEqual( $input.data( 'historize.index' ), null );

    });

    _test( 'it should not extend the history on "Enter" if empty', function() {

        $input.historize().trigger( enter );

        strictEqual( $input.data( 'historize.index' ), null );

        deepEqual( $input.historize( 'get' ), [] );

    });

    _test( 'it should extend the history on "Enter" if not empty', function() {

        $input.historize().val( 'foo' ).trigger( enter );

        strictEqual( $input.data( 'historize.index' ), 0 );

        deepEqual( $input.historize( 'get' ), [ 'foo' ] );

    });

})(jQuery);
<|endoftext|>


<commit_before>/* globals QUnit: false */

QUnit.module('filterPath');

QUnit.test('Link paths not location path', function(assert) {
  var locationPath = $.smoothScroll.filterPath(location.pathname);
  $('a[href*=#]').each(function() {
    assert.notEqual(locationPath, this.pathname);
  });
});

QUnit.module('scrollable');

QUnit.test('Returns first scrollable element (html,body)', function(assert) {
  var scrollable = $('html,body').firstScrollable();
  assert.equal(scrollable.length, 1, 'One scrollable element is returned');
  assert.equal(scrollable[0], document.body, 'Scrollable element is ');
});

QUnit.test('Returns scrollable element (div#scrollable)', function(assert) {
  var scrollable = $('#scrollable').scrollable();
  assert.equal(scrollable.length, 1, 'One scrollable element is returned');
  assert.equal(scrollable[0].id, 'scrollable', 'Scrollable element is
');
});

QUnit.module('$.smoothScroll');
QUnit.test('Scrolls the #scrollable element, or not.', function(assert) {
  var done = assert.async(2);

  $.smoothScroll({
    scrollElement: $('#scrollable'),
    scrollTarget: '#does-not-exist',
    speed: 200,
    afterScroll: function() {
      var scrollTop = $('#scrollable').scrollTop();
      assert.equal(scrollTop, 0, '#scrollable element does not scroll to non-existant element');

      // Trigger the next scroll, which should actually work
      $('#scrollit').trigger('click');
      done();
    }
  });

  $('#scrollit').bind('click', function(event) {
    event.preventDefault();
    $.smoothScroll({
      scrollElement: $('#scrollable'),
      scrollTarget: '#findme',
      speed: 200,
      afterScroll: function() {
        var scrollTop = $('#scrollable').scrollTop();
        assert.equal(scrollTop, 100, '#scrollable element scrolls');
        done();
      }
    });
  });
});

QUnit.test('Scrolls the #scrollable element to absolute or relative scroll position', function(assert) {
  var done = assert.async(3);

  var finalScroll = function() {
    $.smoothScroll({
      scrollElement: $('#scrollable'),
      speed: 100,
      afterScroll: function() {
        var scrollTop = $('#scrollable').scrollTop();
        assert.equal(scrollTop, 10, '#scrollable element scrolls back to 10');

        // Trigger the next scroll, which should actually work
        done();
      }
    }, 10);
  };

  $.smoothScroll({
    scrollElement: $('#scrollable'),
    speed: 100,
    afterScroll: function() {
      var scrollTop = $('#scrollable').scrollTop();
      assert.equal(scrollTop, 15, '#scrollable element scrolls to 15');

      // Trigger the next scroll, which should actually work
      $('#scrollit').trigger('click');
      done();
    }
  }, 15);

  $('#scrollit').bind('click', function(event) {
    event.preventDefault();

    $.smoothScroll({
      scrollElement: $('#scrollable'),
      speed: 100,
      afterScroll: function() {
        var scrollTop = $('#scrollable').scrollTop();
        assert.equal(scrollTop, 25, '#scrollable element scrolls "+=10" to 25');
        done();
        finalScroll();
      }
    }, '+=10');
  });
});
<commit_msg>Lint tests.js<commit_after>/* globals QUnit: false */

QUnit.module('filterPath');

QUnit.test('Link paths not location path', function(assert) {
  var locationPath = $.smoothScroll.filterPath(location.pathname);

  $('a[href*=#]').each(function() {
    assert.notEqual(locationPath, this.pathname);
  });
});

QUnit.module('scrollable');

QUnit.test('Returns first scrollable element (html,body)', function(assert) {
  var scrollable = $('html,body').firstScrollable();

  assert.equal(scrollable.length, 1, 'One scrollable element is returned');
  assert.equal(scrollable[0], document.body, 'Scrollable element is ');
});

QUnit.test('Returns scrollable element (div#scrollable)', function(assert) {
  var scrollable = $('#scrollable').scrollable();

  assert.equal(scrollable.length, 1, 'One scrollable element is returned');
  assert.equal(scrollable[0].id, 'scrollable', 'Scrollable element is
');
});

QUnit.module('$.smoothScroll');
QUnit.test('Scrolls the #scrollable element, or not.', function(assert) {
  var done = assert.async(2);

  $.smoothScroll({
    scrollElement: $('#scrollable'),
    scrollTarget: '#does-not-exist',
    speed: 200,
    afterScroll: function() {
      var scrollTop = $('#scrollable').scrollTop();

      assert.equal(scrollTop, 0, '#scrollable element does not scroll to non-existant element');

      // Trigger the next scroll, which should actually work
      $('#scrollit').trigger('click');
      done();
    }
  });

  $('#scrollit').bind('click', function(event) {
    event.preventDefault();
    $.smoothScroll({
      scrollElement: $('#scrollable'),
      scrollTarget: '#findme',
      speed: 200,
      afterScroll: function() {
        var scrollTop = $('#scrollable').scrollTop();

        assert.equal(scrollTop, 100, '#scrollable element scrolls');
        done();
      }
    });
  });
});

QUnit.test('Scrolls the #scrollable element to absolute or relative scroll position', function(assert) {
  var done = assert.async(3);

  var finalScroll = function() {
    $.smoothScroll({
      scrollElement: $('#scrollable'),
      speed: 100,
      afterScroll: function() {
        var scrollTop = $('#scrollable').scrollTop();

        assert.equal(scrollTop, 10, '#scrollable element scrolls back to 10');

        // Trigger the next scroll, which should actually work
        done();
      }
    }, 10);
  };

  $.smoothScroll({
    scrollElement: $('#scrollable'),
    speed: 100,
    afterScroll: function() {
      var scrollTop = $('#scrollable').scrollTop();

      assert.equal(scrollTop, 15, '#scrollable element scrolls to 15');

      // Trigger the next scroll, which should actually work
      $('#scrollit').trigger('click');
      done();
    }
  }, 15);

  $('#scrollit').bind('click', function(event) {
    event.preventDefault();

    $.smoothScroll({
      scrollElement: $('#scrollable'),
      speed: 100,
      afterScroll: function() {
        var scrollTop = $('#scrollable').scrollTop();

        assert.equal(scrollTop, 25, '#scrollable element scrolls "+=10" to 25');
        done();
        finalScroll();
      }
    }, '+=10');
  });
});
<|endoftext|>


<commit_before>// Initialization --------------------------------------------------------------
$.getJSON("../config.json", function(data) {
  console.log("initialized");
  SC.initialize({
    client_id: data["soundcloud_client_id"]
  });
});

chrome.storage.sync.clear();
var stream = null;
var currentTracks = null;
var currentSongIdx = -1;
// -----------------------------------------------------------------------------
chrome.runtime.onConnect.addListener(function(port) {
  clearPagination();
  displayCurrentSong(port);
  displayPreviousSearch(port);
  port.onMessage.addListener(messageHandler(port));
});

function messageHandler(port) {
  var listener = function(msg) {
    var content = msg.content;
    var message = msg.message;

    switch (message) {
      case "play-song":
        playSong(content.index);
        displayCurrentExtensionTrack(port);
        break;
      case "search":
        var searchInfo = {
          q: content,
          limit: 10,
          linked_partitioning: 1
        };

        SC.get("/tracks", searchInfo).then(function(res) {
          chrome.storage.sync.set({
            previousSearch: searchInfo
          });
          displayTracks(port, res);
        });
        break;
      case "toggle":
        if (!!stream) {
          stream.toggle();
        }
        break;
      case "next-tracks":
        var currentTrackHref = content;
        $.getJSON(currentTrackHref, function(res) {
          displayTracks(port, res);
          chrome.storage.sync.set({
            prevTracks: getPrevHref(currentTrackHref)
          });
        });
        break;
      case "prev-tracks":
        chrome.storage.sync.get("prevTracks", function(obj) {
          if (chrome.runtime.lastError == null && !$.isEmptyObject(obj)) {
            var currentTrackHref = obj.prevTracks;
            $.getJSON(obj.prevTracks, function(res) {
              displayTracks(port, res);
              chrome.storage.sync.set({
                prevTracks: getPrevHref(currentTrackHref)
              });
            });
          }
        });
        break;
      default:
        break;
    }
  };
  // displayCurrentSong(port); // might not need
  return listener;
}

// BUG: you can't replay the same song over. probably because the stream
// doesn't close/is cached? Have to look into this.
function playSong(index) {
  var track = currentTracks[index];

  chrome.storage.sync.set({
    currentTrack: track
  });

  SC.stream("/tracks/" + track.id).then(function(player) {
    stream = player;
    currentSongIdx = index;

    stream.play();

    stream.on("finish", function() {
      chrome.storage.sync.set({
        currentTrack: {}
      });
      // cached result will require a reset
      stream.seek(0);
      // TODO: out-of-bounds handling that would require pagination
      playSong(index + 1);
    });
  });
}

function displayTracks(port, tracksResp) {
  currentTracks = tracksResp.collection;
  port.postMessage({
    message: "display-tracks",
    content: tracksResp
  });
}

function displayPreviousSearch(port) {
  chrome.storage.sync.get(["previousSearch"], function(obj) {
    if (chrome.runtime.lastError == null && "previousSearch" in obj) {
      SC.get("/tracks", obj.previousSearch).then(function(res) {
        displayTracks(port, res);
      });
    }
  });
}

function displayCurrentSong(port) {
  chrome.tabs.query(
    {
      url: "*://soundcloud.com/*"
    },
    function(soundcloudTabs) {
      if (soundcloudTabs.length > 0) {
        var currentlyPlaying = null;
        // TODO: try to not have to look thru all the tabs every time. Caching?
        var audibleTabs = soundcloudTabs.filter(function(t) {
          return t.audible == true;
        });
        if (audibleTabs.length == 0) {
          displayCurrentExtensionTrack(port);
          return;
        }
        currentlyPlaying = audibleTabs[0];
        port.postMessage({
          message: "display-current-track",
          content: currentlyPlaying
        });
      } else {
        displayCurrentExtensionTrack(port);
      }
    }
  );
}

function displayCurrentExtensionTrack(port) {
  chrome.storage.sync.get(["currentTrack"], function(obj) {
    if (
      chrome.runtime.lastError == null &&
      "currentTrack" in obj &&
      !$.isEmptyObject(obj.currentTrack)
    ) {
      port.postMessage({
        message: "display-current-track",
        content: {
          track: obj.currentTrack,
          isPlaying: !!stream && stream.controller.getState() === "playing"
        }
      });
    }
  });
}

function clearPagination() {
  chrome.storage.sync.remove(["prevTracks", "nextTracks"]);
}

// Assumes valid API url from soundcloud
function getPrevHref(url) {
  var oldUrl = url.split("?");
  var params = $.parseParams(oldUrl[1]);
  if (params.offset > 0) {
    params.offset =
      Number.parseInt(params.offset) - Number.parseInt(params.limit);
  }
  return oldUrl[0] + "?" + $.param(params);
}

function getNextHref(url) {
  var oldUrl = url.split("?");
  var params = $.parseParams(oldUrl[1]);
  params.offset =
    Number.parseInt(params.offset) + Number.parseInt(params.limit);
  return oldUrl[0] + "?" + $.param(params);
}

// https://gist.github.com/kares/956897
(function($) {
  var re = /([^&=]+)=?([^&]*)/g;
  var decodeRE = /\+/g; // Regex for replacing addition symbol with a space
  var decode = function(str) {
    return decodeURIComponent(str.replace(decodeRE, " "));
  };
  $.parseParams = function(query) {
    var params = {},
      e;
    while ((e = re.exec(query))) {
      var k = decode(e[1]),
        v = decode(e[2]);
      if (k.substring(k.length - 2) === "[]") {
        k = k.substring(0, k.length - 2);
        (params[k] || (params[k] = [])).push(v);
      } else params[k] = v;
    }
    return params;
  };
})(jQuery);

// --------- KEYBOARD SHORTCUT LISTENERS -------------------------------------
function switchToTabInWindow(tabId, windowId) {
  var windowUpdateInfo = { focused: true };
  chrome.windows.update(windowId, windowUpdateInfo);

  var tabUpdateInfo = { active: true };
  chrome.tabs.update(tabId, tabUpdateInfo);
}

function setPrevPageInfo(tabId, windowId) {
  var newPrevInfo = {
    prevTabId: tabId,
    prevWindowId: windowId
  };
  chrome.storage.sync.set(newPrevInfo);
}

chrome.commands.onCommand.addListener(function(command) {
  switch (command) {
    case "open-soundcloud":
      var activeTabInfo = {
        lastFocusedWindow: true,
        active: true
      };
      chrome.tabs.query(activeTabInfo, function(tabs) {
        if (tabs.length > 0) {
          // only set previous info if the page you are looking at is a
          // "tabbable" window. For example, a develepor tools window. TODO: look more into this.
          setPrevPageInfo(tabs[0].id, tabs[0].windowId);
        }
      });
      var query = {
        url: "*://soundcloud.com/*"
      };
      chrome.tabs.query(query, function(soundcloudTabs) {
        if (soundcloudTabs.length == 0) {
          chrome.tabs.create({
            url: "https://soundcloud.com"
          });
        } else {
          var tabToSwitchTo = null;
          // TODO: try to not have to look thru all the tabs every time. Caching?
          var audibleTabs = soundcloudTabs.filter(function(t) {
            return t.audible == true;
          });
          if (audibleTabs.length > 0) {
            tabToSwitchTo = audibleTabs[0];
          } else {
            tabToSwitchTo = soundcloudTabs[0];
          }
          // TODO: only update if not on tabToSwitchTo
          switchToTabInWindow(tabToSwitchTo.id, tabToSwitchTo.windowId);
        }
      });
      break;
    case "previous-location":
      var prevInfo = ["prevTabId", "prevWindowId"];
      chrome.storage.sync.get(prevInfo, function(prevInfo) {
        if (chrome.runtime.lastError == null) {
          switchToTabInWindow(prevInfo.prevTabId, prevInfo.prevWindowId);
        }
      });
    default:
      break;
  }
});
<commit_msg>unbind on finish handler when song is done<commit_after>// Initialization --------------------------------------------------------------
$.getJSON("../config.json", function(data) {
  console.log("initialized");
  SC.initialize({
    client_id: data["soundcloud_client_id"]
  });
});

chrome.storage.sync.clear();
var stream = null;
var currentTracks = null;
var currentSongIdx = -1;
// -----------------------------------------------------------------------------
chrome.runtime.onConnect.addListener(function(port) {
  clearPagination();
  displayCurrentSong(port);
  displayPreviousSearch(port);
  port.onMessage.addListener(messageHandler(port));
});

function messageHandler(port) {
  var listener = function(msg) {
    var content = msg.content;
    var message = msg.message;

    switch (message) {
      case "play-song":
        if (!!stream) {
          stream.pause();
        }
        playSong(content.index);
        displayCurrentExtensionTrack(port);
        break;
      case "search":
        var searchInfo = {
          q: content,
          limit: 10,
          linked_partitioning: 1
        };

        SC.get("/tracks", searchInfo).then(function(res) {
          chrome.storage.sync.set({
            previousSearch: searchInfo
          });
          displayTracks(port, res);
        });
        break;
      case "toggle":
        if (!!stream) {
          stream.toggle();
        }
        break;
      case "next-tracks":
        var currentTrackHref = content;
        $.getJSON(currentTrackHref, function(res) {
          displayTracks(port, res);
          chrome.storage.sync.set({
            prevTracks: getPrevHref(currentTrackHref)
          });
        });
        break;
      case "prev-tracks":
        chrome.storage.sync.get("prevTracks", function(obj) {
          if (chrome.runtime.lastError == null && !$.isEmptyObject(obj)) {
            var currentTrackHref = obj.prevTracks;
            $.getJSON(obj.prevTracks, function(res) {
              displayTracks(port, res);
              chrome.storage.sync.set({
                prevTracks: getPrevHref(currentTrackHref)
              });
            });
          }
        });
        break;
      default:
        break;
    }
  };
  // displayCurrentSong(port); // might not need
  return listener;
}

// BUG: you can't replay the same song over. probably because the stream
// doesn't close/is cached? Have to look into this.
function playSong(index) {
  var track = currentTracks[index];

  chrome.storage.sync.set({
    currentTrack: track
  });

  SC.stream("/tracks/" + track.id).then(
    function(player) {
      stream = player;
      currentSongIdx = index;

      stream.play();

      stream.on("finish", function() {
        chrome.storage.sync.set({
          currentTrack: {}
        });

        // cached result will require a reset
        stream.off("finish");
        stream.seek(0);

        // TODO: automatically display new current song when popup is open
        // TODO: out-of-bounds handling that would require pagination
        playSong(index + 1);
      });
    },
    function(error) {
      console.log("Streaming error: " + error);
    }
  );
}

function displayTracks(port, tracksResp) {
  currentTracks = tracksResp.collection;
  port.postMessage({
    message: "display-tracks",
    content: tracksResp
  });
}

function displayPreviousSearch(port) {
  chrome.storage.sync.get(["previousSearch"], function(obj) {
    if (chrome.runtime.lastError == null && "previousSearch" in obj) {
      SC.get("/tracks", obj.previousSearch).then(function(res) {
        displayTracks(port, res);
      });
    }
  });
}

function displayCurrentSong(port) {
  chrome.tabs.query(
    {
      url: "*://soundcloud.com/*"
    },
    function(soundcloudTabs) {
      if (soundcloudTabs.length > 0) {
        var currentlyPlaying = null;
        // TODO: try to not have to look thru all the tabs every time. Caching?
        var audibleTabs = soundcloudTabs.filter(function(t) {
          return t.audible == true;
        });
        if (audibleTabs.length == 0) {
          displayCurrentExtensionTrack(port);
          return;
        }
        currentlyPlaying = audibleTabs[0];
        port.postMessage({
          message: "display-current-track",
          content: currentlyPlaying
        });
      } else {
        displayCurrentExtensionTrack(port);
      }
    }
  );
}

function displayCurrentExtensionTrack(port) {
  chrome.storage.sync.get(["currentTrack"], function(obj) {
    if (
      chrome.runtime.lastError == null &&
      "currentTrack" in obj &&
      !$.isEmptyObject(obj.currentTrack)
    ) {
      port.postMessage({
        message: "display-current-track",
        content: {
          track: obj.currentTrack,
          isPlaying: !!stream && stream.controller.getState() === "playing"
        }
      });
    }
  });
}

function clearPagination() {
  chrome.storage.sync.remove(["prevTracks", "nextTracks"]);
}

// Assumes valid API url from soundcloud
function getPrevHref(url) {
  var oldUrl = url.split("?");
  var params = $.parseParams(oldUrl[1]);
  if (params.offset > 0) {
    params.offset =
      Number.parseInt(params.offset) - Number.parseInt(params.limit);
  }
  return oldUrl[0] + "?" + $.param(params);
}

function getNextHref(url) {
  var oldUrl = url.split("?");
  var params = $.parseParams(oldUrl[1]);
  params.offset =
    Number.parseInt(params.offset) + Number.parseInt(params.limit);
  return oldUrl[0] + "?" + $.param(params);
}

// https://gist.github.com/kares/956897
(function($) {
  var re = /([^&=]+)=?([^&]*)/g;
  var decodeRE = /\+/g; // Regex for replacing addition symbol with a space
  var decode = function(str) {
    return decodeURIComponent(str.replace(decodeRE, " "));
  };
  $.parseParams = function(query) {
    var params = {},
      e;
    while ((e = re.exec(query))) {
      var k = decode(e[1]),
        v = decode(e[2]);
      if (k.substring(k.length - 2) === "[]") {
        k = k.substring(0, k.length - 2);
        (params[k] || (params[k] = [])).push(v);
      } else params[k] = v;
    }
    return params;
  };
})(jQuery);

// --------- KEYBOARD SHORTCUT LISTENERS -------------------------------------
function switchToTabInWindow(tabId, windowId) {
  var windowUpdateInfo = { focused: true };
  chrome.windows.update(windowId, windowUpdateInfo);

  var tabUpdateInfo = { active: true };
  chrome.tabs.update(tabId, tabUpdateInfo);
}

function setPrevPageInfo(tabId, windowId) {
  var newPrevInfo = {
    prevTabId: tabId,
    prevWindowId: windowId
  };
  chrome.storage.sync.set(newPrevInfo);
}

chrome.commands.onCommand.addListener(function(command) {
  switch (command) {
    case "open-soundcloud":
      var activeTabInfo = {
        lastFocusedWindow: true,
        active: true
      };
      chrome.tabs.query(activeTabInfo, function(tabs) {
        if (tabs.length > 0) {
          // only set previous info if the page you are looking at is a
          // "tabbable" window. For example, a develepor tools window. TODO: look more into this.
          setPrevPageInfo(tabs[0].id, tabs[0].windowId);
        }
      });
      var query = {
        url: "*://soundcloud.com/*"
      };
      chrome.tabs.query(query, function(soundcloudTabs) {
        if (soundcloudTabs.length == 0) {
          chrome.tabs.create({
            url: "https://soundcloud.com"
          });
        } else {
          var tabToSwitchTo = null;
          // TODO: try to not have to look thru all the tabs every time. Caching?
          var audibleTabs = soundcloudTabs.filter(function(t) {
            return t.audible == true;
          });
          if (audibleTabs.length > 0) {
            tabToSwitchTo = audibleTabs[0];
          } else {
            tabToSwitchTo = soundcloudTabs[0];
          }
          // TODO: only update if not on tabToSwitchTo
          switchToTabInWindow(tabToSwitchTo.id, tabToSwitchTo.windowId);
        }
      });
      break;
    case "previous-location":
      var prevInfo = ["prevTabId", "prevWindowId"];
      chrome.storage.sync.get(prevInfo, function(prevInfo) {
        if (chrome.runtime.lastError == null) {
          switchToTabInWindow(prevInfo.prevTabId, prevInfo.prevWindowId);
        }
      });
    default:
      break;
  }
});
<|endoftext|>


<commit_before>var _     = require('lodash');
var React = require('react/addons');
var Input = require('../../resource-editing/resource-form/Input');

/**
 * @class PageSearch
 *
 * @memberOf VisualizationComponents
 */
var PageSearch = React.createClass({
	propTypes: {
		searchable: React.PropTypes.array.isRequired,
		minLength: React.PropTypes.number,
		options: React.PropTypes.shape({
			queryTimeout: React.PropTypes.number
		})
	},

	/**
	 * Gets the initial state of the component
	 *
	 * @memberOf VisualizationComponents.PageSearch
	 *
	 * @public
	 * @return {object}
	 */
	getInitialState: function () {
		return {
			search: {
				query: ''
			}
		}
	},

	/**
	 * Container for the current timeout ID
	 * @memberOf VisualizationComponents.PageSearch
	 */
	currentTimeout: 0,

	/**
	 * Build a filters object with the structure:
	 *
	 * ```{
	 *   "field1": "query",
	 *   "or": {
	 *     "field2": "query",
	 *     ....
	 *   }
	 * }```
	 *
	 * @memberOf VisualizationComponents.PageSearch
	 *
	 * @param {String} query
	 * @param {Array} fields
	 * @param {Object} filters
	 *
	 * @return {object}
	 * @private
	 */
	_buildFilterObject: function (query, fields, filters, nested) {
		nested = nested || false;
		var _buildFilterObject = this._buildFilterObject;
		fields.forEach(function (field, index) {
			if (! nested && index === 0) {
				filters[field] = '~' + query;
			} else if (filters.hasOwnProperty('or')) {
				_buildFilterObject(query, [field], filters.or, true);
			} else {
				filters.or = {};
				filters.or[field] = '~' + query;
			}
		});

		return filters;
	},

	/**
	 * Handle search field updates
	 * @memberOf VisualizationComponents.PageSearch
	 *
	 * @param {String} key
	 * @param {String} value
	 * @param {Object} component
	 *
	 * @return {null}
	 * @private
	 */
	_handleSearchChange: function (key, value, component) {
		var options = this.props.options || {};

		// Clear the previous setTimeout callback and register a new one while a user keeps typing
		clearTimeout(this.currentTimeout);
		delete this.currentTimeout;

		var queryData = this.queryData;
		this.currentTimeout = setTimeout(function () {
			queryData();
		}, options.queryTimeout || 500);

		this.setState({
			search: {
				query: value
			}
		})
	},

	/**
	 * Manage query string
	 * @memberOf VisualizationComponents.PageSearch
	 *
	 * @return {null}
	 */
	queryData: function () {
		var filters = {};
		var query = this.state.search.query;
		var minLengths = this.props.minLength || 1;

		if (query.length < this.props.minLength) {
			// Don't set any modification filters
		} else {
			filters = this._buildFilterObject(query, this.props.searchable, {});
		}

		this.props.actionMethod({filters: filters});
	},

	/**
	 * Renders the page search box
	 * @memberOf VisualizationComponents.PageSearch
	 *
	 * @return {jsx}
	 */
	render: function () {
		return (

				<Input fieldKey="query" bindResource={this.state.search} changeCallback={this._handleSearchChange}
					placeholder="Search.." type="text" />

					<button className="btn btn-default" type="button" onClick={this.queryData}>

					</button>

		)
	}
});

module.exports = PageSearch;
<commit_msg>Fixing an issue when typing into page search then deleting all the input<commit_after>var _     = require('lodash');
var React = require('react/addons');
var Input = require('../../resource-editing/resource-form/Input');

/**
 * @class PageSearch
 *
 * @memberOf VisualizationComponents
 */
var PageSearch = React.createClass({
	propTypes: {
		searchable: React.PropTypes.array.isRequired,
		minLength: React.PropTypes.number,
		options: React.PropTypes.shape({
			queryTimeout: React.PropTypes.number
		})
	},

	/**
	 * Gets the initial state of the component
	 *
	 * @memberOf VisualizationComponents.PageSearch
	 *
	 * @public
	 * @return {object}
	 */
	getInitialState: function () {
		return {
			search: {
				query: ''
			}
		}
	},

	/**
	 * Container for the current timeout ID
	 * @memberOf VisualizationComponents.PageSearch
	 */
	currentTimeout: 0,

	/**
	 * Build a filters object with the structure:
	 *
	 * ```{
	 *   "field1": "query",
	 *   "or": {
	 *     "field2": "query",
	 *     ....
	 *   }
	 * }```
	 *
	 * @memberOf VisualizationComponents.PageSearch
	 *
	 * @param {String} query
	 * @param {Array} fields
	 * @param {Object} filters
	 *
	 * @return {object}
	 * @private
	 */
	_buildFilterObject: function (query, fields, filters, nested) {
		if (! query) { return {}; } // If the search field is blank, return no filters

		nested = nested || false;
		var _buildFilterObject = this._buildFilterObject;
		fields.forEach(function (field, index) {
			if (! nested && index === 0) {
				filters[field] = '~' + query;
			} else if (filters.hasOwnProperty('or')) {
				_buildFilterObject(query, [field], filters.or, true);
			} else {
				filters.or = {};
				filters.or[field] = '~' + query;
			}
		});

		return filters;
	},

	/**
	 * Handle search field updates
	 * @memberOf VisualizationComponents.PageSearch
	 *
	 * @param {String} key
	 * @param {String} value
	 * @param {Object} component
	 *
	 * @return {null}
	 * @private
	 */
	_handleSearchChange: function (key, value, component) {
		var options = this.props.options || {};

		// Clear the previous setTimeout callback and register a new one while a user keeps typing
		clearTimeout(this.currentTimeout);
		delete this.currentTimeout;

		var queryData = this.queryData;
		this.currentTimeout = setTimeout(function () {
			queryData();
		}, options.queryTimeout || 500);

		this.setState({
			search: {
				query: value
			}
		})
	},

	/**
	 * Manage query string
	 * @memberOf VisualizationComponents.PageSearch
	 *
	 * @return {null}
	 */
	queryData: function () {
		var filters = {};
		var query = this.state.search.query;
		var minLengths = this.props.minLength || 1;

		if (query.length < this.props.minLength) {
			// Don't set any modification filters
		} else {
			filters = this._buildFilterObject(query, this.props.searchable, {});
		}

		this.props.actionMethod({filters: filters});
	},

	/**
	 * Renders the page search box
	 * @memberOf VisualizationComponents.PageSearch
	 *
	 * @return {jsx}
	 */
	render: function () {
		return (

				<Input fieldKey="query" bindResource={this.state.search} changeCallback={this._handleSearchChange}
					placeholder="Search.." type="text" />

					<button className="btn btn-default" type="button" onClick={this.queryData}>

					</button>

		)
	}
});

module.exports = PageSearch;
<|endoftext|>


<commit_before>/*!
 * Connect - methodOverride
 * Copyright(c) 2010 Sencha Inc.
 * Copyright(c) 2011 TJ Holowaychuk
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var methods = require('methods');

/**
 * Method Override:
 *
 * Provides faux HTTP method support.
 *
 * Pass an optional `key` to use when checking for
 * a method override, otherwise defaults to _\_method_.
 * The original method is available via `req.originalMethod`.
 *
 * @param {String} key
 * @return {Function}
 * @api public
 */

module.exports = function methodOverride(key){
  key = key || "_method";
  return function methodOverride(req, res, next) {
    var method;
    req.originalMethod = req.originalMethod || req.method;

    // req.body
    if (req.body && key in req.body) {
      method = req.body[key].toLowerCase();
      delete req.body[key];
    }

    // check X-HTTP-Method-Override
    if (req.headers['x-http-method-override']) {
      method = req.headers['x-http-method-override'].toLowerCase();
    }

    // replace
    if (supports(method)) req.method = method.toUpperCase();

    next();
  };
};

/**
 * Check if node supports `method`.
 */

function supports(method) {
  return ~methods.indexOf(method);
}
<commit_msg>added typeof test of body to method override<commit_after>/*!
 * Connect - methodOverride
 * Copyright(c) 2010 Sencha Inc.
 * Copyright(c) 2011 TJ Holowaychuk
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var methods = require('methods');

/**
 * Method Override:
 *
 * Provides faux HTTP method support.
 *
 * Pass an optional `key` to use when checking for
 * a method override, otherwise defaults to _\_method_.
 * The original method is available via `req.originalMethod`.
 *
 * @param {String} key
 * @return {Function}
 * @api public
 */

module.exports = function methodOverride(key){
  key = key || "_method";
  return function methodOverride(req, res, next) {
    var method;
    req.originalMethod = req.originalMethod || req.method;

    // req.body
    if (req.body && typeof req.body === 'object' && key in req.body) {
      method = req.body[key].toLowerCase();
      delete req.body[key];
    }

    // check X-HTTP-Method-Override
    if (req.headers['x-http-method-override']) {
      method = req.headers['x-http-method-override'].toLowerCase();
    }

    // replace
    if (supports(method)) req.method = method.toUpperCase();

    next();
  };
};

/**
 * Check if node supports `method`.
 */

function supports(method) {
  return ~methods.indexOf(method);
}
<|endoftext|>


<commit_before>(function() {
	main = function() {
		this.logDebug = function(msg) {
			console.log("[background] " + msg);
		};

		this.KEY_URL_INFOS = "key_urlInfos";

		// A handler to handle BeforeRequest
		this.requestBlockFunction = function(details) {
			this.logDebug("Blocked URL: " + details.url);
			return {cancel: true};
		};

		// url: "http://www.google.com/", etc.
		//  ->> "*://www.google.com/*", etc.
		this.formatUrl = function(url) {
			var re = /^https?:\/\/([\w.]+)\/.*/;
			return url.replace(re, "*://$1/*");
		};

		chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
			if (request.action) {
				switch (request.action) {
					case "startTrack":
						this.logDebug("startTrack");
						var key = this.KEY_URL_INFOS;
						var formatUrl = this.formatUrl;
						var requestBlockFunction = this.requestBlockFunction;
						chrome.storage.sync.get(key, function(items) {
							var urlInfos = JSON.parse(items[key]);
							var urls = [];
							urlInfos.forEach(function(urlInfo, index) {
								var formatted = formatUrl(urlInfo.url);  // "*://host/*"
								urls.push(formatted);  // "*://host/*"
								urlInfo.formattedUrl = formatted;
							}, this);

							if (urls.length > 0) {
								chrome.webRequest.onBeforeRequest.addListener(
									requestBlockFunction,
									{urls: urls},  // "*://host/*"
									["blocking"]
								);
							}

							var object = {};
							object[key] = JSON.stringify(urlInfos);
							chrome.storage.sync.set(object, function() {
								console.log("saved:");
								console.log(object);
							});
						});
						break;
					case "stopTrack":
						this.logDebug("stopTrack");
						chrome.webRequest.onBeforeRequest.removeListener(
							this.requestBlockFunction
						);
						break;
					case "restartTrack":
						this.logDebug("restartTrack");
						this.logDebug("   !!! under implementation !!!");
						// stopTrack
						// startTrack
						break;
					case "start":
						chrome.tabs.query({ currentWindow: true, active: true }, function (tabs) {
							console.log("location.host: " + request.host);
							console.log("tabs[0]: " + tabs[0]);
							console.log("tabs[0].url: " + tabs[0].url);
							chrome.tabs.sendMessage(tabs[0].id, {action: "block"}, function(response) {
								if (response.isSuccess)
									console.log("block done.");
								else
									console.log("block failed...");
							});
						});
						break;
					default:
						console.log("Undefined action...");
						break;
				}
			}
		});
	};

	main();
}).call(this);

<commit_msg>Count blocked URL<commit_after>(function() {
	main = function() {
		this.logDebug = function(msg) {
			console.log("[background] " + msg);
		};

		this.KEY_URL_INFOS = "key_urlInfos";

		// A handler to handle BeforeRequest
		this.requestBlockFunction = function(details) {
			this.logDebug("Blocked URL: " + details.url);
			this.incrementBlockedCount(details.url);
			return {cancel: true};
		};

		this.incrementBlockedCount = function(blockedUrl) {
			var key = this.KEY_URL_INFOS;
			var formatUrl = this.formatUrl;
			chrome.storage.sync.get(key, function(items) {
				var urlInfos = JSON.parse(items[key]);
				var urls = [];
				var needSave = false;
				urlInfos.forEach(function(urlInfo, index) {
					var formattedBlockedUrl = formatUrl(blockedUrl);
					if (formattedBlockedUrl === urlInfo.formattedUrl) {
						urlInfo.blocked++; // increment
						needSave = true;
					}
				}, this);

				if (needSave) {
					var object = {};
					object[key] = JSON.stringify(urlInfos);
					chrome.storage.sync.set(object, function() {
						console.log("saved:");
						console.log(object);
					});
				}
			});
		};

		// url: "http://www.google.com/", etc.
		//  ->> "*://www.google.com/*", etc.
		this.formatUrl = function(url) {
			var re = /^https?:\/\/([\w.]+)\/.*/;
			return url.replace(re, "*://$1/*");
		};

		chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
			if (request.action) {
				switch (request.action) {
					case "startTrack":
						this.logDebug("startTrack");
						var key = this.KEY_URL_INFOS;
						var formatUrl = this.formatUrl;
						var requestBlockFunction = this.requestBlockFunction;
						chrome.storage.sync.get(key, function(items) {
							var urlInfos = JSON.parse(items[key]);
							var urls = [];
							urlInfos.forEach(function(urlInfo, index) {
								var formatted = formatUrl(urlInfo.url);  // "*://host/*"
								urls.push(formatted);  // "*://host/*"
								urlInfo.formattedUrl = formatted;
							}, this);

							if (urls.length > 0) {
								chrome.webRequest.onBeforeRequest.addListener(
									requestBlockFunction,
									{urls: urls},  // "*://host/*"
									["blocking"]
								);
							}

							// Save formatted URL
							var object = {};
							object[key] = JSON.stringify(urlInfos);
							chrome.storage.sync.set(object, function() {
								console.log("saved:");
								console.log(object);
							});
						});
						break;
					case "stopTrack":
						this.logDebug("stopTrack");
						chrome.webRequest.onBeforeRequest.removeListener(
							this.requestBlockFunction
						);
						break;
					case "restartTrack":
						this.logDebug("restartTrack");
						this.logDebug("   !!! under implementation !!!");
						// stopTrack
						// startTrack
						break;
					case "start":
						chrome.tabs.query({ currentWindow: true, active: true }, function (tabs) {
							console.log("location.host: " + request.host);
							console.log("tabs[0]: " + tabs[0]);
							console.log("tabs[0].url: " + tabs[0].url);
							chrome.tabs.sendMessage(tabs[0].id, {action: "block"}, function(response) {
								if (response.isSuccess)
									console.log("block done.");
								else
									console.log("block failed...");
							});
						});
						break;
					default:
						console.log("Undefined action...");
						break;
				}
			}
		});
	};

	main();
}).call(this);

<|endoftext|>


<commit_before>'use strict';

exports = module.exports = Controllers;

var Controller = require('./controller');

var util = require('util');
var path = require('path');
var debug = require('debug')('fire:controllers');
var inflection = require('inflection');
var Q = require('q');
var config = require('./../../helpers/config');

/**
 * The Controllers module.
 *
 * @param {App} app The app initializing this module.
 *
 * @constructor
 */
function Controllers(app) {
	// When the app is disabled, we do not have an express instance.

	this.app 	= app;
	this._cache = [];
	this._controllers = {};

	var self = this;
	var appType = app.settings('type');
	if(appType == 'angular' || appType == 'ractive') {
		app.controller = function(controllerPath, controllerConstructor) {
			if(controllerConstructor) {
				self.controller(controllerPath, controllerConstructor);
			}
			else {
				self.controller(null, controllerPath);
			}
		};
	}
	else {
		throw new Error('Unknown app type `' + appType + '`. Maybe this is only available in a future Node on Fire version?');
	}
}

/**
* Sets up a controller.
*
* The `controllerConstructor` gets inherited by {@link Controller} and the constructor gets added to the system so routes can be generated and additional code can be generated.
*
* Please note: the constructor itself, is __never__ executed in the server-context. Instead, the constructor is passed to angular's controller method and a controller is created on the client-context.
*
* ```js
* function MyController($scope) {
* 	console.log(MyController);
* }
* app.controller(MyController);
*
* MyController.prototype.view = function() {
* 	return this.template('index');
* };
* ```
*
* The Bridge module is responsible for generating the client-context controller. This Controllers module checks the controllerConstructor's prototype and creates any routes.
*
* @param  {Constructor} controllerConstructor The named controller constructor.
*/
Controllers.prototype.controller = function(controllerPath, controllerConstructor) {
	util.inherits(controllerConstructor, Controller);

	// We delay because when app.controller(MyController) is called, we have to wait before the complete prototype is set.
	var self = this;
	setImmediate(function() {
		self.addControllerConstructor(controllerPath, controllerConstructor);
	});
};

Controllers.prototype.stages = ['build', 'release', 'run'];

/**
 * Convenience method to loop over all controllers.
 *
 * @access private
 *
 * @param {Function} callback(controllerConstructor) The callback function is invoked for every controller.
 */
Controllers.prototype.forEach = function(callback) {
	var controllerNames = Object.keys(this._controllers);
	for(var i = 0, il = controllerNames.length; i < il; i++) {
		var controllerConstructor = this._controllers[controllerNames[i]];
		callback(controllerConstructor);
	}
};

/**
 * Loads all files in the `controllers/` directory and sets up all already loaded controllers.
 *
 * Setup is invoked by the app when it's starting up.
 *
 * @access private
 *
 * @param  {String} basePath The root path of the project.
 * @return {Promise}
 */
Controllers.prototype.setup = function(basePath) {
	var defer = Q.defer();

	if(this.app.HTTPServer && this.app.HTTPServer.express) {
		var keys = ['1038641b2d8e106ea60850034b43d7a9'];

		if(process.env.SESSION_KEYS) {
			keys = process.env.SESSION_KEYS.split(',');
		}
		else {
			if(!process.env.NODE_ENV || process.env.NODE_ENV != 'development' && process.env.NODE_ENV != 'test') {
				console.log('WARNING: Specify SESSION_KEYS in your .env to properly configure cookie session.');
			}
		}

		this.app.HTTPServer.express.use(require('cookie-session')({
			keys: keys,
			name: 'sid',
			domain: process.env.COOKIE_DOMAIN,
			maxAge: Number(process.env.SESSION_MAX_AGE_MS) || 1000 * 60 * 60 * 24 * 7 * 52 * 10
		}));
		this.app.HTTPServer.express.use(require('body-parser')());

		// TODO: Create a separate module for this.
		this.app.HTTPServer.express.use(function(request, response, next) {
			if((request.headers['x-json-params'] == 1 || request.headers['x-json-params'] === true || request.headers['x-json-params'] === 'true' || request.headers['x-json-params'] == '1')) {
				var parseJSONParams = function(source) {
					var dest = {};

					Object.keys(source).forEach(function(key) {
						try {
							dest[key] = JSON.parse(source[key]);
						}
						catch(e) {
							debug(e);
						}
					});

					return dest;
				};

				request.params = parseJSONParams(request.params);
				request.query = parseJSONParams(request.query);
				request.body = parseJSONParams(request.body);
			}

			next();
		});
	}

	debug('Controllers#setup');

	// Everything gets added in the cache.
	if(basePath) {
		debug(path.join(basePath, 'controllers'));

		this.app.requireDirSync(path.join(basePath, 'controllers'));
	}

	// We delay the execution as the prototype isn't fully declared yet.
	// See Models#setup for more details.
	var self = this;
	setImmediate(function() {
		// We get the cache and invalidate it.
		var cache = self._cache;
		self._cache = null;

		cache.forEach(function(controller) {
			self.loadControllerConstructor(controller.path, controller.constructor, null);
		});

		debug('~Controllers#setup (' + Object.keys(self._controllers).length + ' controllers)');

		defer.resolve();
	});

	return defer.promise;
};

/**
 * Adds `controllerConstructor` to the load queue, or loads the constructor if setup already finished.
 *
 * If the controllers are already loaded (the setup is finished) immediately loads a controller. Because the constructor's prototype might not be completely set yet, this method delays execution with a setImmediate.
 *
 * @access private
 *
 * @param {Constructor} controllerConstructor The controller constructor to be loaded.
 */
Controllers.prototype.addControllerConstructor = function(controllerPath, controllerConstructor) {
	debug('addController ' + controllerConstructor.name);

	if(this._cache) {
		this._cache.push({
			path: controllerPath,
			constructor: controllerConstructor
		});
	}
	else {
		this.loadControllerConstructor(controllerPath, controllerConstructor, null);
	}
};

/**
 * Loads the controller and creates the routes based on the controller's methods.
 *
 * There is no separate routing table, but the router creates the routes based on the controllers' methods. For more information on route, see Router.
 *
 * @access private
 *
 * @param {Constructor} controllerConstructor
 * @param {String} fullPath              The path to the constructor's file
 */
Controllers.prototype.loadControllerConstructor = function(controllerPath, controllerConstructor, fullPath) {
	debug('loadControllerConstructor `' + controllerConstructor.name + '`.');
	console.log(fullPath);

	// Now we add the controller class (it's actually a constructor) to the controllers.
	// This will build the routes to the controller.

	if(!(controllerConstructor.prototype instanceof Controller)) {
		throw new Error('Controller in `' + fullPath + '` is not an instance of Controller. Did you call app.controller(...) on your controller?');
	}

	var controllerPaths = [];
	if(Array.isArray(controllerPath)) {
		controllerPaths = controllerPath.map(function(path) {
			return {path: path};
		});
	}
	else if(controllerPath) {
		controllerPaths = [{path: controllerPath}];
	}

	if(controllerPaths) {
		this.addViewRoute(controllerPaths, controllerConstructor);
	}

	// TODO: deprecate the view-based routes, which is the old syntax to declare routes. The new syntax is defined in the middleware module.
	var router = controllerConstructor.prototype.getRouter(controllerConstructor, fullPath);
	router.delegate = this;
	router.createRoutes();

	if(this._controllers[controllerConstructor.name]) {
		this._controllers[controllerConstructor.name].paths = this._controllers[controllerConstructor.name].paths.concat(controllerPaths);
	}
	else {

		this._controllers[controllerConstructor.name] = {
			paths: controllerPaths,
			templatePath: '/templates/' + inflection.dasherize(inflection.underscore(controllerConstructor.name.replace(/Controller$/, '')).toLowerCase()) + '.html',
			constructor: controllerConstructor,
			tests: []
		};
	}
};

/**
 * Creates a view route to the path which returns the main template.
 *
 * @param {String} controllerPath        The path of the view.
 * @param {String} controllerConstructor The controller.
 */
Controllers.prototype.addViewRoute = function(controllerPaths, controllerConstructor) {
	if(!this.app.HTTPServer || !this.app.HTTPServer.express) {
		return;
	}

	var page = null;
	var self = this;

	if(controllerConstructor.prototype.page) {
		page = controllerConstructor.prototype.page();
	}

	if(!page) {
		page = {};
	}

	if(!page.template) {
		if(self.app.container.numberOfApps() > 1) {
			page.template = '.fire/.build/public/' + self.app.name + '/templates/view.html';
		}
		else {
			page.template = '.fire/.build/public/templates/view.html';
		}
	}

	if(self.app._settings.stylesheets) {
		throw new Error('Please change `stylesheets` with `styles` in Fire#app.');
	}

	var options = self.app._settings || {};
	options._fire = {
		appName: self.app.name
	};

	if(page.styles) {
		options.styles = page.styles;
	}

	if(page.scripts) {
		options.scripts = page.scripts;
	}

	if(!Array.isArray(controllerPaths)) {
		throw new Error('Controllers#addViewRoute requires first parameter to be an array.');
	}

	controllerPaths.forEach(function(controllerPath) {
		this.app.HTTPServer.express.get(controllerPath.path, function parseView(request, response) {
			var html = self.app.templates.template(page.template);
			if(html) {
				response.status(200).send(html);
			}
			else {
				response.render(path.join(config.basePath, page.template), options, function renderView(error, text) {
					if(error) {
						throw error;
					}
					else {
						response.status(200).send(text);
					}
				});
			}
		});
	}, this);
};

/**
 * This is a route delegate method and is invoked whenever the router creates a route for a controller's method.
 *
 * If the route is a view route, a single GET route is created. A method is considered a view route if the method's name starts with view and returns a Template instance. A route to the view—returning the application's main view.
 *
 * Consider the following view method:
 *
 * ```js
 * TestController.prototype.view = function() {
 * 	return this.template('index.jade');
 * };
 * ```
 *
 * This view method would produce a route to `GET /` returning the app's view.
 *
 * The app's default view is view.jade and is used as main template in angular. The view should contain the ngApp and ngView directives.
 *
 * If the route is not a view route, a controller route is created.
 *
 * When a request matches the route, roughly the following happens:
 *
 * 	1. A controller is allocated and initialized.
 * 	2. Controller#configure() is called with the NODE_ENV.
 * 	3. Controller#before is called.
 * 	4. The controller's method the route is linked to is called.
 * 	5. The return value of the controller's method is send as JSON to the client.
 * 	6. Controller#after is called.
 *
 * If during any of the steps an error occurs, an error is send with a proper HTTP status code.
 *
 * @param {Route} route The route to be added.
 */
Controllers.prototype.addRoute = function(route) {
	if(!this.app.HTTPServer || !this.app.HTTPServer.express) {
		return;
	}

	var self = this;

	// If this is a view route, simply add it to the views instead.
	if(route.isView) {
		debug('addView ' + route.path);

		this.addViewRoute([{path: route.path}], route.controllerConstructor);
	}
	else {
		debug('addRoute ' + route.verb + ' ' + route.path);

		this.app.HTTPServer.express[route.verb](route.path, function parseConnection(request, response) {
			debug('parseConnection ' + request.url);

			// TODO: Cache this instance and do not allocate a new instance on every request.
			var TemporaryController = function __TemporaryController() {};
			util.inherits(TemporaryController, route.controllerConstructor);

			var controller = new TemporaryController();
			Controller.call(controller, self.app.models, self.app.workers, request, response);

			// TODO: Deprecate this.
			controller.configure(process.env.NODE_ENV || 'development');

			Q.when(controller.before())
				.then(function() {
					return route.method.apply(controller, route.argumentNames.map(function(argumentName) {
						if(argumentName[0] == '$' && argumentName.length > 1) {
							return request.params[argumentName.substring(1)];
						}
						else {
							return request.param(argumentName);
						}
					}));
				})
				.then(function(result) {
					// TODO: Add another hook to "transform" the result.
					// TODO: Return a promise in this scope, resolve it later on (in callbacks/stream).
					if(result) {
						response.json(result);
					}
					else {
						response.status(404).send();
					}
				})
				.then(function() {
					return controller.after();
				})
				.catch(function(error) {
					debug(error);

					// TODO: This should go through Route#sendResponse instead.

					if(error) {
						response.status(error.status || 500).send({
							error: error.message
						});
					}
					else {
						response.status(500).send({error: 'Internal Server Error'});
					}
				})
				.done();
		});
	}
};
<commit_msg>removes logs<commit_after>'use strict';

exports = module.exports = Controllers;

var Controller = require('./controller');

var util = require('util');
var path = require('path');
var debug = require('debug')('fire:controllers');
var inflection = require('inflection');
var Q = require('q');
var config = require('./../../helpers/config');

/**
 * The Controllers module.
 *
 * @param {App} app The app initializing this module.
 *
 * @constructor
 */
function Controllers(app) {
	// When the app is disabled, we do not have an express instance.

	this.app 	= app;
	this._cache = [];
	this._controllers = {};

	var self = this;
	var appType = app.settings('type');
	if(appType == 'angular' || appType == 'ractive') {
		app.controller = function(controllerPath, controllerConstructor) {
			if(controllerConstructor) {
				self.controller(controllerPath, controllerConstructor);
			}
			else {
				self.controller(null, controllerPath);
			}
		};
	}
	else {
		throw new Error('Unknown app type `' + appType + '`. Maybe this is only available in a future Node on Fire version?');
	}
}

/**
* Sets up a controller.
*
* The `controllerConstructor` gets inherited by {@link Controller} and the constructor gets added to the system so routes can be generated and additional code can be generated.
*
* Please note: the constructor itself, is __never__ executed in the server-context. Instead, the constructor is passed to angular's controller method and a controller is created on the client-context.
*
* ```js
* function MyController($scope) {
* 	console.log(MyController);
* }
* app.controller(MyController);
*
* MyController.prototype.view = function() {
* 	return this.template('index');
* };
* ```
*
* The Bridge module is responsible for generating the client-context controller. This Controllers module checks the controllerConstructor's prototype and creates any routes.
*
* @param  {Constructor} controllerConstructor The named controller constructor.
*/
Controllers.prototype.controller = function(controllerPath, controllerConstructor) {
	util.inherits(controllerConstructor, Controller);

	// We delay because when app.controller(MyController) is called, we have to wait before the complete prototype is set.
	var self = this;
	setImmediate(function() {
		self.addControllerConstructor(controllerPath, controllerConstructor);
	});
};

Controllers.prototype.stages = ['build', 'release', 'run'];

/**
 * Convenience method to loop over all controllers.
 *
 * @access private
 *
 * @param {Function} callback(controllerConstructor) The callback function is invoked for every controller.
 */
Controllers.prototype.forEach = function(callback) {
	var controllerNames = Object.keys(this._controllers);
	for(var i = 0, il = controllerNames.length; i < il; i++) {
		var controllerConstructor = this._controllers[controllerNames[i]];
		callback(controllerConstructor);
	}
};

/**
 * Loads all files in the `controllers/` directory and sets up all already loaded controllers.
 *
 * Setup is invoked by the app when it's starting up.
 *
 * @access private
 *
 * @param  {String} basePath The root path of the project.
 * @return {Promise}
 */
Controllers.prototype.setup = function(basePath) {
	var defer = Q.defer();

	if(this.app.HTTPServer && this.app.HTTPServer.express) {
		var keys = ['1038641b2d8e106ea60850034b43d7a9'];

		if(process.env.SESSION_KEYS) {
			keys = process.env.SESSION_KEYS.split(',');
		}
		else {
			if(!process.env.NODE_ENV || process.env.NODE_ENV != 'development' && process.env.NODE_ENV != 'test') {
				console.log('WARNING: Specify SESSION_KEYS in your .env to properly configure cookie session.');
			}
		}

		this.app.HTTPServer.express.use(require('cookie-session')({
			keys: keys,
			name: 'sid',
			domain: process.env.COOKIE_DOMAIN,
			maxAge: Number(process.env.SESSION_MAX_AGE_MS) || 1000 * 60 * 60 * 24 * 7 * 52 * 10
		}));
		this.app.HTTPServer.express.use(require('body-parser')());

		// TODO: Create a separate module for this.
		this.app.HTTPServer.express.use(function(request, response, next) {
			if((request.headers['x-json-params'] == 1 || request.headers['x-json-params'] === true || request.headers['x-json-params'] === 'true' || request.headers['x-json-params'] == '1')) {
				var parseJSONParams = function(source) {
					var dest = {};

					Object.keys(source).forEach(function(key) {
						try {
							dest[key] = JSON.parse(source[key]);
						}
						catch(e) {
							debug(e);
						}
					});

					return dest;
				};

				request.params = parseJSONParams(request.params);
				request.query = parseJSONParams(request.query);
				request.body = parseJSONParams(request.body);
			}

			next();
		});
	}

	debug('Controllers#setup');

	// Everything gets added in the cache.
	if(basePath) {
		debug(path.join(basePath, 'controllers'));

		this.app.requireDirSync(path.join(basePath, 'controllers'));
	}

	// We delay the execution as the prototype isn't fully declared yet.
	// See Models#setup for more details.
	var self = this;
	setImmediate(function() {
		// We get the cache and invalidate it.
		var cache = self._cache;
		self._cache = null;

		cache.forEach(function(controller) {
			self.loadControllerConstructor(controller.path, controller.constructor, null);
		});

		debug('~Controllers#setup (' + Object.keys(self._controllers).length + ' controllers)');

		defer.resolve();
	});

	return defer.promise;
};

/**
 * Adds `controllerConstructor` to the load queue, or loads the constructor if setup already finished.
 *
 * If the controllers are already loaded (the setup is finished) immediately loads a controller. Because the constructor's prototype might not be completely set yet, this method delays execution with a setImmediate.
 *
 * @access private
 *
 * @param {Constructor} controllerConstructor The controller constructor to be loaded.
 */
Controllers.prototype.addControllerConstructor = function(controllerPath, controllerConstructor) {
	debug('addController ' + controllerConstructor.name);

	if(this._cache) {
		this._cache.push({
			path: controllerPath,
			constructor: controllerConstructor
		});
	}
	else {
		this.loadControllerConstructor(controllerPath, controllerConstructor, null);
	}
};

/**
 * Loads the controller and creates the routes based on the controller's methods.
 *
 * There is no separate routing table, but the router creates the routes based on the controllers' methods. For more information on route, see Router.
 *
 * @access private
 *
 * @param {Constructor} controllerConstructor
 * @param {String} fullPath              The path to the constructor's file
 */
Controllers.prototype.loadControllerConstructor = function(controllerPath, controllerConstructor, fullPath) {
	debug('loadControllerConstructor `' + controllerConstructor.name + '`.');

	// Now we add the controller class (it's actually a constructor) to the controllers.
	// This will build the routes to the controller.

	if(!(controllerConstructor.prototype instanceof Controller)) {
		throw new Error('Controller in `' + fullPath + '` is not an instance of Controller. Did you call app.controller(...) on your controller?');
	}

	var controllerPaths = [];
	if(Array.isArray(controllerPath)) {
		controllerPaths = controllerPath.map(function(path) {
			return {path: path};
		});
	}
	else if(controllerPath) {
		controllerPaths = [{path: controllerPath}];
	}

	if(controllerPaths) {
		this.addViewRoute(controllerPaths, controllerConstructor);
	}

	// TODO: deprecate the view-based routes, which is the old syntax to declare routes. The new syntax is defined in the middleware module.
	var router = controllerConstructor.prototype.getRouter(controllerConstructor, fullPath);
	router.delegate = this;
	router.createRoutes();

	if(this._controllers[controllerConstructor.name]) {
		this._controllers[controllerConstructor.name].paths = this._controllers[controllerConstructor.name].paths.concat(controllerPaths);
	}
	else {

		this._controllers[controllerConstructor.name] = {
			paths: controllerPaths,
			templatePath: '/templates/' + inflection.dasherize(inflection.underscore(controllerConstructor.name.replace(/Controller$/, '')).toLowerCase()) + '.html',
			constructor: controllerConstructor,
			tests: []
		};
	}
};

/**
 * Creates a view route to the path which returns the main template.
 *
 * @param {String} controllerPath        The path of the view.
 * @param {String} controllerConstructor The controller.
 */
Controllers.prototype.addViewRoute = function(controllerPaths, controllerConstructor) {
	if(!this.app.HTTPServer || !this.app.HTTPServer.express) {
		return;
	}

	var page = null;
	var self = this;

	if(controllerConstructor.prototype.page) {
		page = controllerConstructor.prototype.page();
	}

	if(!page) {
		page = {};
	}

	if(!page.template) {
		if(self.app.container.numberOfApps() > 1) {
			page.template = '.fire/.build/public/' + self.app.name + '/templates/view.html';
		}
		else {
			page.template = '.fire/.build/public/templates/view.html';
		}
	}

	if(self.app._settings.stylesheets) {
		throw new Error('Please change `stylesheets` with `styles` in Fire#app.');
	}

	var options = self.app._settings || {};
	options._fire = {
		appName: self.app.name
	};

	if(page.styles) {
		options.styles = page.styles;
	}

	if(page.scripts) {
		options.scripts = page.scripts;
	}

	if(!Array.isArray(controllerPaths)) {
		throw new Error('Controllers#addViewRoute requires first parameter to be an array.');
	}

	controllerPaths.forEach(function(controllerPath) {
		this.app.HTTPServer.express.get(controllerPath.path, function parseView(request, response) {
			var html = self.app.templates.template(page.template);
			if(html) {
				response.status(200).send(html);
			}
			else {
				response.render(path.join(config.basePath, page.template), options, function renderView(error, text) {
					if(error) {
						throw error;
					}
					else {
						response.status(200).send(text);
					}
				});
			}
		});
	}, this);
};

/**
 * This is a route delegate method and is invoked whenever the router creates a route for a controller's method.
 *
 * If the route is a view route, a single GET route is created. A method is considered a view route if the method's name starts with view and returns a Template instance. A route to the view—returning the application's main view.
 *
 * Consider the following view method:
 *
 * ```js
 * TestController.prototype.view = function() {
 * 	return this.template('index.jade');
 * };
 * ```
 *
 * This view method would produce a route to `GET /` returning the app's view.
 *
 * The app's default view is view.jade and is used as main template in angular. The view should contain the ngApp and ngView directives.
 *
 * If the route is not a view route, a controller route is created.
 *
 * When a request matches the route, roughly the following happens:
 *
 * 	1. A controller is allocated and initialized.
 * 	2. Controller#configure() is called with the NODE_ENV.
 * 	3. Controller#before is called.
 * 	4. The controller's method the route is linked to is called.
 * 	5. The return value of the controller's method is send as JSON to the client.
 * 	6. Controller#after is called.
 *
 * If during any of the steps an error occurs, an error is send with a proper HTTP status code.
 *
 * @param {Route} route The route to be added.
 */
Controllers.prototype.addRoute = function(route) {
	if(!this.app.HTTPServer || !this.app.HTTPServer.express) {
		return;
	}

	var self = this;

	// If this is a view route, simply add it to the views instead.
	if(route.isView) {
		debug('addView ' + route.path);

		this.addViewRoute([{path: route.path}], route.controllerConstructor);
	}
	else {
		debug('addRoute ' + route.verb + ' ' + route.path);

		this.app.HTTPServer.express[route.verb](route.path, function parseConnection(request, response) {
			debug('parseConnection ' + request.url);

			// TODO: Cache this instance and do not allocate a new instance on every request.
			var TemporaryController = function __TemporaryController() {};
			util.inherits(TemporaryController, route.controllerConstructor);

			var controller = new TemporaryController();
			Controller.call(controller, self.app.models, self.app.workers, request, response);

			// TODO: Deprecate this.
			controller.configure(process.env.NODE_ENV || 'development');

			Q.when(controller.before())
				.then(function() {
					return route.method.apply(controller, route.argumentNames.map(function(argumentName) {
						if(argumentName[0] == '$' && argumentName.length > 1) {
							return request.params[argumentName.substring(1)];
						}
						else {
							return request.param(argumentName);
						}
					}));
				})
				.then(function(result) {
					// TODO: Add another hook to "transform" the result.
					// TODO: Return a promise in this scope, resolve it later on (in callbacks/stream).
					if(result) {
						response.json(result);
					}
					else {
						response.status(404).send();
					}
				})
				.then(function() {
					return controller.after();
				})
				.catch(function(error) {
					debug(error);

					// TODO: This should go through Route#sendResponse instead.

					if(error) {
						response.status(error.status || 500).send({
							error: error.message
						});
					}
					else {
						response.status(500).send({error: 'Internal Server Error'});
					}
				})
				.done();
		});
	}
};
<|endoftext|>