<commit_before>var sys = require('sys');
var fs = require('fs');
var errors = require('./errors');
var response = require('./response');
var fleegix = require('./fleegix');

var Controller = function (name, params, req, resp) {
  // Content-type the controller can respond with -- assume
  // minimum of plaintext
  this.respondsWith = ['text'];
  // The name of the controller constructor function,
  // in CamelCase with uppercase initial letter
  this.name = name;
  // The name, in lowercase_with_underscores, used for
  // picking the template if any to attempt to render
  this.nameDeCamelized = fleegix.string.deCamelize(name);
  // The action gets passed these as an argument, but we keep
  // them here too to have access to the file extension, so
  // we can do content-negotiation
  this.params = params;
  // The http.ServerRequest passed to the 'request' event
  // callback function
  this.request = req;
  // The http.ServerResponse passed to the 'request' event
  // callback function
  this.response = resp;
  // Content to respond with -- can be an Object or String
  this.content = '';
  // High-level set of options which can represent multiple
  // content-types
  // 'text', 'json', 'xml', 'html'
  this.format = '';
  // Content-type of the response -- driven by the format, and
  // by what content-types the client accepts
  this.contentType = '';
};

Controller.prototype = new function () {
  
  this.formatters = {
    json: function (content) {
      return JSON.stringify(content);
    },
    text: function (content) {
      return JSON.stringify(content);
    }
  };
  
  this.respond = function (content, format) {
    
    // format and contentType are set at the same time
    var negotiated = this.negotiateContent(format);
    this.format = negotiated.format;
    this.contentType = negotiated.contentType;

    if (!this.contentType) {
      throw new errors.NotAcceptableError('Not an acceptable media type.');
    }
    
    this.formatContentAndFinish(content);
  };

  this.finish = function () {
    var r = new response.Response(this.response);
    r.send(this.content, this.contentType);
  };

  this.negotiateContent = function (frmt) {
    var format;
    var contentType;
    var types;
    var match;
    var params = this.params;
    if (frmt) {
      types = [frmt];
    }
    else if (params.extension) {
      var t = response.contentTypes[params.extension];
      var f = response.formatsReverseMap[t];
      if (f && ('|' + this.respondsWith.join('|') + '|').indexOf(
          '|' + f + '|') > -1) {
        types = [f];
      }
      else {
        throw new errors.NotAcceptableError('Not an acceptable media type.');
      }
    }
    else {
      types = this.respondsWith;
    }
    
    // Look for a specific match
    var a = this.request.headers.accept.split(',');
    var i;
    var pat;
    var wildcard = false;

    // Ignore quality factors for now
    for (i = 0; i < a.length; i++) {
      a[i] = a[i].split(';')[0];
      if (a[i] == '*/*') {
        wildcard = true;
      }
    }
    for (i = 0; i < types.length; i++) {
      pat = response.formatPatterns[types[i]];
      if (pat) { 
        for (var j = 0; j < a.length; j++) {
          match = a[j].match(pat);
          if (match) {
            format = types[i];
            contentType = match;
            break;
          }
        }
      }
      // If respondsWith contains an unknown format
      // TODO: Better error to tell devs how to set up new formats
      else {
        throw new errors.InternalServerError('Unknown format');
      }
      // Don't look at any more formats if there's a match
      if (match) {
        break;
      }
    }
    
    if (!contentType && wildcard) {
      t = types[0];
      format = t;
      match = response.formats[t];
      if (match) {
        contentType = match.split('|')[0];
      }
    }
   
    if (!(format && contentType)) {
      throw new errors.InternalServerError('Unknown format');
    }
    
    return {format: format, contentType: contentType};
  };

  this.formatContentAndFinish = function (content) {
    if (typeof content == 'string') {
      this.content = content;
      this.finish();
    }
    else { 
      if (this.format) {
        this.formatContent(this.format, content);
      }
      else {
        throw new errors.InternalServerError('Unknown format');
      }
    }
  };

  this.formatContent = function (format, content) {
    if (format == 'html') {
      var _this = this;
      var name = this.nameDeCamelized;
      var act = this.params.action;
      var path = 'app/views/' + name + '/' + act + '.html.ejs';
      fs.readFile(path, 'utf8', function (err, data) {
        if (err) { throw err; }
        var templ = new fleegix.ejs.Template({text: data});
        templ.process({data: content});
        _this.formatContentAndFinish(templ.markup);
      });
    }
    else {
      var c = this.formatters[format](content);
      this.formatContentAndFinish(c);
    }
  };

}();

exports.Controller = Controller;
<commit_msg>Added support for recursively async-loading partials.<commit_after>var sys = require('sys');
var fs = require('fs');
var errors = require('./errors');
var response = require('./response');
var templates = require('./templates');
var fleegix = require('./fleegix');

var Controller = function (name, params, req, resp) {
  // Content-type the controller can respond with -- assume
  // minimum of plaintext
  this.respondsWith = ['text'];
  // The name of the controller constructor function,
  // in CamelCase with uppercase initial letter
  this.name = name;
  // The name, in lowercase_with_underscores, used for
  // picking the template if any to attempt to render
  this.nameDeCamelized = fleegix.string.deCamelize(name);
  // The action gets passed these as an argument, but we keep
  // them here too to have access to the file extension, so
  // we can do content-negotiation
  this.params = params;
  // The http.ServerRequest passed to the 'request' event
  // callback function
  this.request = req;
  // The http.ServerResponse passed to the 'request' event
  // callback function
  this.response = resp;
  // Content to respond with -- can be an Object or String
  this.content = '';
  // High-level set of options which can represent multiple
  // content-types
  // 'text', 'json', 'xml', 'html'
  this.format = '';
  // Content-type of the response -- driven by the format, and
  // by what content-types the client accepts
  this.contentType = '';

  this.currentPartialId = 0;

  this.baseTemplateNode = null;
};

Controller.prototype = new function () {
  
  this.formatters = {
    json: function (content) {
      return JSON.stringify(content);
    },
    text: function (content) {
      return JSON.stringify(content);
    }
  };
  
  this.respond = function (content, format) {
    
    // format and contentType are set at the same time
    var negotiated = this.negotiateContent(format);
    this.format = negotiated.format;
    this.contentType = negotiated.contentType;

    if (!this.contentType) {
      throw new errors.NotAcceptableError('Not an acceptable media type.');
    }
    
    this.formatContentAndFinish(content);
  };

  this.finish = function () {
    var r = new response.Response(this.response);
    r.send(this.content, this.contentType);
  };

  this.negotiateContent = function (frmt) {
    var format;
    var contentType;
    var types;
    var match;
    var params = this.params;
    if (frmt) {
      types = [frmt];
    }
    else if (params.extension) {
      var t = response.contentTypes[params.extension];
      var f = response.formatsReverseMap[t];
      if (f && ('|' + this.respondsWith.join('|') + '|').indexOf(
          '|' + f + '|') > -1) {
        types = [f];
      }
      else {
        throw new errors.NotAcceptableError('Not an acceptable media type.');
      }
    }
    else {
      types = this.respondsWith;
    }
    
    // Look for a specific match
    var a = this.request.headers.accept.split(',');
    var i;
    var pat;
    var wildcard = false;

    // Ignore quality factors for now
    for (i = 0; i < a.length; i++) {
      a[i] = a[i].split(';')[0];
      if (a[i] == '*/*') {
        wildcard = true;
      }
    }
    for (i = 0; i < types.length; i++) {
      pat = response.formatPatterns[types[i]];
      if (pat) { 
        for (var j = 0; j < a.length; j++) {
          match = a[j].match(pat);
          if (match) {
            format = types[i];
            contentType = match;
            break;
          }
        }
      }
      // If respondsWith contains an unknown format
      // TODO: Better error to tell devs how to set up new formats
      else {
        throw new errors.InternalServerError('Unknown format');
      }
      // Don't look at any more formats if there's a match
      if (match) {
        break;
      }
    }
    
    if (!contentType && wildcard) {
      t = types[0];
      format = t;
      match = response.formats[t];
      if (match) {
        contentType = match.split('|')[0];
      }
    }
   
    if (!(format && contentType)) {
      throw new errors.InternalServerError('Unknown format');
    }
    
    return {format: format, contentType: contentType};
  };

  this.formatContentAndFinish = function (content) {
    if (typeof content == 'string') {
      this.content = content;
      this.finish();
    }
    else { 
      if (this.format) {
        this.formatContent(this.format, content);
      }
      else {
        throw new errors.InternalServerError('Unknown format');
      }
    }
  };

  this.partial = function (partial, params, parentNode) {
    var _this = this;
    var node;
    var partialId = this.currentPartialId;
    
    // Curry the partial method to use the current node as the
    // parent in subsequent calls
    params.partial = function (part, parm) {
      return _this.partial.call(_this, part, parm, node);
    };
    
    // Create the current node, with a reference to its parent, if any
    node = new templates.TemplateNode(partialId, partial, params, parentNode);
    // If there is a parent, add this node as its child
    if (parentNode) {
      parentNode.childNodes[partialId] = node;
    }
      
    // If this is the base node (i.e., there's no baseTemplateNode yet),
    // give this node the finishRoot method that actually renders the final,
    // completed content for the entire template
    if (!this.baseTemplateNode) {
      node.finishRoot = function () {
        _this.formatContentAndFinish(_this.baseTemplateNode.content);
      }
      this.baseTemplateNode = node;
      // Kick off the hierarchical async loading process
      node.loadTemplate();
    }
    
    // Increment the current partial id for the next call
    this.currentPartialId++;
    
    // Return the placeholder text to represent this template -- it gets
    // replaced in the callback from the async load of the actual content
    return '###partial###' + partialId;
  };

  this.formatContent = function (format, content) {
    if (format == 'html') {
      var _this = this;
      var name = this.nameDeCamelized;
      var act = this.params.action;
      // E.g., app/views/snow_dogs/index.html.ejs
      var path = 'app/views/' + name + '/' + act + '.html.ejs';
      
      this.partial(path, content);
      return;
    }
    else {
      var c = this.formatters[format](content);
      this.formatContentAndFinish(c);
    }
  };

}();

exports.Controller = Controller;

{'fraction_non_alphanumeric': 0.12679238724254802, 'fraction_numerical': 0.0013904579820978535, 'mean_word_length': 2.314516129032258, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1308237', 'n_tokens_mistral': 3466, 'n_tokens_neox': 3402, 'n_words': 1028}