text: jasmine.ConsoleReporter = function(print, doneCallback, showColors) {
    //inspired by mhevery's jasmine-node reporter
    //https://github.com/mhevery/jasmine-node
  
    doneCallback = doneCallback || function() {};
  
    var ansi = {
        green: '\033[32m',
        red: '\033[31m',
        yellow: '\033[33m',
        none: '\033[0m'
      },
      language = {
        spec: "spec",
        failure: "failure"
      };
  
    function coloredStr(color, str) {
      return showColors ? (ansi[color] + str + ansi.none) : str;
    }
  
    function greenStr(str) {
      return coloredStr("green", str);
    }
  
    function redStr(str) {
      return coloredStr("red", str);
    }
  
    function yellowStr(str) {
      return coloredStr("yellow", str);
    }
  
    function newline() {
      print("\n");
    }
  
    function started() {
      print("Started");
      newline();
    }
  
    function greenPass() {
      print(greenStr("PASS"));
    }
  
    function redFail() {
      print(redStr("FAIL"));
    }
  
    function yellowStar() {
      print(yellowStr("*"));
    }
  
    function plural(str, count) {
      return count == 1 ? str : str + "s";
    }
  
    function repeat(thing, times) {
      var arr = [];
      for (var i = 0; i < times; i++) {
        arr.push(thing);
      }
      return arr;
    }
  
    function indent(str, spaces) {
      var lines = (str || '').split("\n");
      var newArr = [];
      for (var i = 0; i < lines.length; i++) {
        newArr.push(repeat(" ", spaces).join("") + lines[i]);
      }
      return newArr.join("\n");
    }
  
    function specFailureDetails(suiteDescription, specDescription, stackTraces) {
      newline();
      print(suiteDescription + " " + specDescription);
      for (var i = 0; i < stackTraces.length; i++) {
        print(indent(stackTraces[i], 2));
      }
    }
  
    function finished(elapsed) {
      newline();
      print("Finished in " + elapsed / 1000 + " seconds");
    }
  
    function summary(colorF, specs, failed) {
      newline();
      print(colorF(specs + " " + plural(language.spec, specs) + ", " +
        failed + " " + plural(language.failure, failed)));
    }
  
    function greenSummary(specs, failed) {
      summary(greenStr, specs, failed);
    }
  
    function redSummary(specs, failed) {
      summary(redStr, specs, failed);
    }
  
    function fullSuiteDescription(suite) {
      var fullDescription = suite.description;
      if (suite.parentSuite) fullDescription = fullSuiteDescription(suite.parentSuite) + " " + fullDescription;
      return fullDescription;
    }
  
    this.now = function() {
      return new Date().getTime();
    };
  
    this.reportRunnerStarting = function() {
      this.runnerStartTime = this.now();
      started();
    };
  
    this.reportSpecStarting = function() { /* do nothing */
    };
  
    this.reportSpecResults = function(spec) {
      var results = spec.results();
      if (results.skipped) {
        yellowStar();
      } else {
        if (results.passed()) {
          print('#' + spec.id + ' ' + spec.suite.description + ': ' + spec.description);
          greenPass();
        } else {
          print(redStr('#' + spec.id + ' ' + spec.suite.description + ': ' + spec.description));
          redFail();
        }
      }
    };
  
    this.suiteResults = [];
  
    this.reportSuiteResults = function(suite) {
      var suiteResult = {
        description: fullSuiteDescription(suite),
        failedSpecResults: []
      };
  
      suite.results().items_.forEach(function(spec) {
        if (spec.failedCount > 0 && spec.description) suiteResult.failedSpecResults.push(spec);
      });
  
      this.suiteResults.push(suiteResult);
    };
  
    function eachSpecFailure(suiteResults, callback) {
      for (var i = 0; i < suiteResults.length; i++) {
        var suiteResult = suiteResults[i];
        for (var j = 0; j < suiteResult.failedSpecResults.length; j++) {
          var failedSpecResult = suiteResult.failedSpecResults[j];
          var stackTraces = [];
          for (var k = 0; k < failedSpecResult.items_.length; k++) stackTraces.push(failedSpecResult.items_[k].trace.stack);
          callback(suiteResult.description, failedSpecResult.description, stackTraces);
        }
      }
    }
  
    this.reportRunnerResults = function(runner) {
      eachSpecFailure(this.suiteResults, function(suiteDescription, specDescription, stackTraces) {
        specFailureDetails(suiteDescription, specDescription, stackTraces);
      });
  
      finished(this.now() - this.runnerStartTime);
  
      var results = runner.results();
      var summaryFunction = results.failedCount === 0 ? greenSummary : redSummary;
      summaryFunction(runner.specs().length, results.failedCount);
      doneCallback(runner);
    };
  };
  