<commit_before>// this function is also present in assignment-edit-views.js, we need to consolidate using moment.js

function formatDate(date) {
  if (date && !isNaN(date.getTime())) {
    return date.getFullYear() + "/" + (date.getMonth() + 1) + "/" + date.getDate();
  } else return null;
}

FLOW.QuestionAnswerView = Ember.View.extend({

  isTextType: function(){
    return this.get('questionType') === 'FREE_TEXT';
  }.property('this.questionType'),

  isCascadeType: function(){
    return this.get('questionType') === 'CASCADE';
  }.property('this.questionType'),

  isOptionType: function(){
    return this.get('questionType') === 'OPTION';
  }.property('this.questionType'),

  isNumberType: function(){
    return this.get('questionType') === 'NUMBER';
  }.property('this.questionType'),

  isBarcodeType: function(){
    return this.get('questionType') === 'SCAN';
  }.property('this.questionType'),

  isDateType: function(){
    return this.get('questionType') === 'DATE';
  }.property('this.questionType'),

  isPhotoType: function(){
    return this.get('questionType') === 'PHOTO';
  }.property('this.questionType'),

  isVideoType: function(){
    return this.get('questionType') === 'VIDEO';
  }.property('this.questionType'),

  isGeoShapeType: function(){
    return this.get('questionType') === 'GEOSHAPE';
  }.property('this.questionType'),

  nonEditableQuestionTypes: ['GEO', 'PHOTO', 'VIDEO', 'GEOSHAPE'],

  form: function() {
    if (FLOW.selectedControl.get('selectedSurvey')) {
      return FLOW.selectedControl.get('selectedSurvey');
    }
  }.property('FLOW.selectedControl.selectedSurvey'),

  /*
   * Get the full list of options related to a particular option type question
   */
  optionsList: function(){
    var c = this.content;
    if (Ember.none(c) || !this.get('isOptionType')) {
      return Ember.A([]);
    }

    var questionId = c.get('questionID');

    var options = FLOW.store.filter(FLOW.QuestionOption, function (item) {
      return item.get('questionId') === +questionId;
    });

    optionArray = options.toArray();
    optionArray.sort(function (a, b) {
        return a.get('order') - b.get('order');
    });

    tempList = Ember.A([]);
    var obj;
    optionArray.forEach(function (item) {
      obj = Ember.Object.create({
        code : item.get('code'),
        text : item.get('text')
      });
      tempList.push(obj);
    });
    return tempList;
  }.property('this.content,this.isOptionType'),

  content: null,

  inEditMode: false,

  isNotEditable: function() {
    // keep this property to limit template rafactoring
    return !this.get('isEditable');
  }.property('this.isEditable'),

  isEditable: function () {
    var isEditableQuestionType, canEditFormResponses;
    isEditableQuestionType = this.nonEditableQuestionTypes.indexOf(this.get('questionType')) < 0;
    if (!isEditableQuestionType) {
      return false; // no need to check permissions
    }

    canEditFormResponses = FLOW.permControl.canEditResponses(this.get('form'));
    return isEditableQuestionType && canEditFormResponses;
  }.property('this.questionType,this.form'),

  date: null,

  numberValue: null,

  cascadeValue: function(key, value, previousValue){
    var c = this.content;
    // setter
    if (arguments.length > 1) {
      // split according to pipes
      var cascadeNames = value.split("|");
      var cascadeResponse = [];
      var obj = null;
      cascadeNames.forEach(function(item){
        if (item.trim().length > 0) {
          obj = {};
          obj.name = item.trim();
          cascadeResponse.push(obj);
        }
      });

      c.set('value', JSON.stringify(cascadeResponse));
    }

    // getter
    var cascadeString = "", cascadeJson;
    if (c && c.get('value')) {
      if (c.get('value').indexOf("|") > -1) {
        cascadeString += c.get('value');
      } else {
        cascadeJson = JSON.parse(c.get('value'));
        cascadeString = cascadeJson.map(function(item){
          return item.name;
        }).join("|");
      }
      return cascadeString;
    }
    return null;
  }.property('this.content'),

  /*
   *  A view property to support the getting and setting of old and new
   *  format values for OPTION type question items / lists.
   *
   *  The setter takes an object and correctly transforms it into an
   *  array that is later stored in the value property as a string
   *
   *  The getter retrieves the string response from the value property and
   *  transforms it into an Ember array consisting of elements from
   *  the optionsList property.  We return elements from the optionList
   *  in order to handle binding of the view elements to the value. Retrieved
   *  responses could be:
   *   - pipe-separated strings for legacy format e.g. 'text1|text2'
   *   - JSON string in the current format e.g
   *    '[{text: "text with code", code: "code"}]'
   *    '[{text: "only text"}]'
   *  An ARRAY is always returned if the content property of the
   *  view is set.
   */
  optionValue: function (key, value, previousValue) {
    var val, optionsList, textArray = [], selectedOptions = Ember.A(), c = this.content;
    //setter
    if (c && arguments.length > 1) {
    }

    // getter
    if (c && c.get('value')) {
      val = c.get('value');
      optionsList = this.get('optionsList');

      if (val.charAt(0) === '[') {
        // responses in JSON format
        JSON.parse(val).forEach(function (response) {
          optionsList.forEach(function (optionObj, index) {
            if (response.text === optionObj.get('text') &&
                response.code === optionObj.get('code')) {
              selectedOptions.addObject(optionObj);
            }
          });
        });
      } else {
        // responses in pipe separated format
        val.split("|").forEach(function(item){
          if (item.trim().length > 0) {
            textArray.push(item.trim());
          }
        });

        optionsList.forEach(function(optionObj, i) {
          if (textArray.indexOf(optionObj.get('text')) > -1) {
            selectedOptions.addObject(optionObj);
          }
        });
      }
      return selectedOptions;
    }
    return null;
  }.property('this.content,this.optionsList'),

  selectedOptions: function () {
    var options = this.get('optionValue');
    if (options && options.get('length') > 1) {
      return options;
    } else {
      return options.get('firstObject');
    }
  }.property('this.optionValue'),

  isMultipleSelectOption: function () {
    return this.get('isOptionType') && this.get('question').get('allowMultipleFlag');
  }.property('this.isOptionType'),

  photoUrl: function(){
    var c = this.content;
    if (!Ember.empty(c.get('value'))) {
      return FLOW.Env.photo_url_root + c.get('value').split('/').pop();
    }
  }.property('this.content,this.isPhotoType,this.isVideoType'),

  geoShapeObject: function(){
    var c = this.content;
    if (!Ember.empty(c.get('value'))) {
      return c.get('value');
    }
  }.property('this.content,this.isGeoShapeType'),

  questionType: function(){
    if(this.get('question')){
      return this.get('question').get('type');
    }
  }.property('this.question'),

  question: function(){
    var c = this.get('content');
    if (c) {
      var questionId = c.get('questionID');
      var q = FLOW.questionControl.findProperty('keyId', +questionId);
      return q;
    }
  }.property('FLOW.questionControl.content'),

  init: function () {
    this._super();
  },

  doEdit: function () {
    this.set('inEditMode', true);
    var c = this.content;

    if (this.get('isDateType') && !Ember.empty(c.get('value'))) {
      var d = new Date(+c.get('value')); // need to coerce c.get('value') due to milliseconds
      this.set('date', formatDate(d));
    }

    if (this.get('isNumberType') && !Ember.empty(c.get('value'))) {
      this.set('numberValue', c.get('value'));
    }
  },

  doCancel: function () {
    this.set('inEditMode', false);
  },

  doSave: function () {

    if (this.get('isDateType')) {
      var d = Date.parse(this.get('date'));
      if (isNaN(d) || d < 0) {
        this.content.set('value', null);
      } else {
        this.content.set('value', d);
      }
    }

    if (this.get('isNumberType')) {
      if (isNaN(this.numberValue)) {
        this.content.set('value', null);
      } else {
        this.content.set('value', this.numberValue);
      }
    }
    FLOW.store.commit();
    this.set('inEditMode', false);

  },

  doValidateNumber: function () {
    // TODO should check for minus sign and decimal point, depending on question setting
    this.set('numberValue', this.get('numberValue').toString().replace(/[^\d.]/g, ""));
  }.observes('this.numberValue')
});

FLOW.QuestionAnswerOptionListView = Ember.CollectionView.extend({
  tagName: 'ul',
  content: null,
  itemViewClass: Ember.View.extend({
    template: Ember.Handlebars.compile("{{view.content.text}}")
  })
});

FLOW.QuestionAnswerInspectDataView = FLOW.QuestionAnswerView.extend({
  templateName: 'navData/question-answer',
});
<commit_msg>[#828] Clean up description of `optionValue` property<commit_after>// this function is also present in assignment-edit-views.js, we need to consolidate using moment.js

function formatDate(date) {
  if (date && !isNaN(date.getTime())) {
    return date.getFullYear() + "/" + (date.getMonth() + 1) + "/" + date.getDate();
  } else return null;
}

FLOW.QuestionAnswerView = Ember.View.extend({

  isTextType: function(){
    return this.get('questionType') === 'FREE_TEXT';
  }.property('this.questionType'),

  isCascadeType: function(){
    return this.get('questionType') === 'CASCADE';
  }.property('this.questionType'),

  isOptionType: function(){
    return this.get('questionType') === 'OPTION';
  }.property('this.questionType'),

  isNumberType: function(){
    return this.get('questionType') === 'NUMBER';
  }.property('this.questionType'),

  isBarcodeType: function(){
    return this.get('questionType') === 'SCAN';
  }.property('this.questionType'),

  isDateType: function(){
    return this.get('questionType') === 'DATE';
  }.property('this.questionType'),

  isPhotoType: function(){
    return this.get('questionType') === 'PHOTO';
  }.property('this.questionType'),

  isVideoType: function(){
    return this.get('questionType') === 'VIDEO';
  }.property('this.questionType'),

  isGeoShapeType: function(){
    return this.get('questionType') === 'GEOSHAPE';
  }.property('this.questionType'),

  nonEditableQuestionTypes: ['GEO', 'PHOTO', 'VIDEO', 'GEOSHAPE'],

  form: function() {
    if (FLOW.selectedControl.get('selectedSurvey')) {
      return FLOW.selectedControl.get('selectedSurvey');
    }
  }.property('FLOW.selectedControl.selectedSurvey'),

  /*
   * Get the full list of options related to a particular option type question
   */
  optionsList: function(){
    var c = this.content;
    if (Ember.none(c) || !this.get('isOptionType')) {
      return Ember.A([]);
    }

    var questionId = c.get('questionID');

    var options = FLOW.store.filter(FLOW.QuestionOption, function (item) {
      return item.get('questionId') === +questionId;
    });

    optionArray = options.toArray();
    optionArray.sort(function (a, b) {
        return a.get('order') - b.get('order');
    });

    tempList = Ember.A([]);
    var obj;
    optionArray.forEach(function (item) {
      obj = Ember.Object.create({
        code : item.get('code'),
        text : item.get('text')
      });
      tempList.push(obj);
    });
    return tempList;
  }.property('this.content,this.isOptionType'),

  content: null,

  inEditMode: false,

  isNotEditable: function() {
    // keep this property to limit template rafactoring
    return !this.get('isEditable');
  }.property('this.isEditable'),

  isEditable: function () {
    var isEditableQuestionType, canEditFormResponses;
    isEditableQuestionType = this.nonEditableQuestionTypes.indexOf(this.get('questionType')) < 0;
    if (!isEditableQuestionType) {
      return false; // no need to check permissions
    }

    canEditFormResponses = FLOW.permControl.canEditResponses(this.get('form'));
    return isEditableQuestionType && canEditFormResponses;
  }.property('this.questionType,this.form'),

  date: null,

  numberValue: null,

  cascadeValue: function(key, value, previousValue){
    var c = this.content;
    // setter
    if (arguments.length > 1) {
      // split according to pipes
      var cascadeNames = value.split("|");
      var cascadeResponse = [];
      var obj = null;
      cascadeNames.forEach(function(item){
        if (item.trim().length > 0) {
          obj = {};
          obj.name = item.trim();
          cascadeResponse.push(obj);
        }
      });

      c.set('value', JSON.stringify(cascadeResponse));
    }

    // getter
    var cascadeString = "", cascadeJson;
    if (c && c.get('value')) {
      if (c.get('value').indexOf("|") > -1) {
        cascadeString += c.get('value');
      } else {
        cascadeJson = JSON.parse(c.get('value'));
        cascadeString = cascadeJson.map(function(item){
          return item.name;
        }).join("|");
      }
      return cascadeString;
    }
    return null;
  }.property('this.content'),

  /*
   *  A view property to retrieve the string response from the value property
   *  of an option question and use it to create an Ember array consisting
   *  of the corresponding elements from the optionsList property, i.e, we return
   *  selected elements of the optionsList.
   *
   *  Retrieved responses could be:
   *   - pipe-separated strings for legacy format e.g. 'text1|text2'
   *   - JSON string in the current format e.g
   *    '[{text: "text with code", code: "code"}]'
   *    '[{text: "only text"}]'
   */
  optionValue: function (key, value, previousValue) {
    var val, optionsList, textArray = [], selectedOptions = Ember.A(), c = this.content;

    if (c && c.get('value')) {
      val = c.get('value');
      optionsList = this.get('optionsList');

      if (val.charAt(0) === '[') {
        // responses in JSON format
        JSON.parse(val).forEach(function (response) {
          optionsList.forEach(function (optionObj, index) {
            if (response.text === optionObj.get('text') &&
                response.code === optionObj.get('code')) {
              selectedOptions.addObject(optionObj);
            }
          });
        });
      } else {
        // responses in pipe separated format
        val.split("|").forEach(function(item){
          if (item.trim().length > 0) {
            textArray.push(item.trim());
          }
        });

        optionsList.forEach(function(optionObj, i) {
          if (textArray.indexOf(optionObj.get('text')) > -1) {
            selectedOptions.addObject(optionObj);
          }
        });
      }
      return selectedOptions;
    }
    return null;
  }.property('this.content,this.optionsList'),

  selectedOptions: function () {
    var options = this.get('optionValue');
    if (options && options.get('length') > 1) {
      return options;
    } else {
      return options.get('firstObject');
    }
  }.property('this.optionValue'),

  isMultipleSelectOption: function () {
    return this.get('isOptionType') && this.get('question').get('allowMultipleFlag');
  }.property('this.isOptionType'),

  photoUrl: function(){
    var c = this.content;
    if (!Ember.empty(c.get('value'))) {
      return FLOW.Env.photo_url_root + c.get('value').split('/').pop();
    }
  }.property('this.content,this.isPhotoType,this.isVideoType'),

  geoShapeObject: function(){
    var c = this.content;
    if (!Ember.empty(c.get('value'))) {
      return c.get('value');
    }
  }.property('this.content,this.isGeoShapeType'),

  questionType: function(){
    if(this.get('question')){
      return this.get('question').get('type');
    }
  }.property('this.question'),

  question: function(){
    var c = this.get('content');
    if (c) {
      var questionId = c.get('questionID');
      var q = FLOW.questionControl.findProperty('keyId', +questionId);
      return q;
    }
  }.property('FLOW.questionControl.content'),

  init: function () {
    this._super();
  },

  doEdit: function () {
    this.set('inEditMode', true);
    var c = this.content;

    if (this.get('isDateType') && !Ember.empty(c.get('value'))) {
      var d = new Date(+c.get('value')); // need to coerce c.get('value') due to milliseconds
      this.set('date', formatDate(d));
    }

    if (this.get('isNumberType') && !Ember.empty(c.get('value'))) {
      this.set('numberValue', c.get('value'));
    }
  },

  doCancel: function () {
    this.set('inEditMode', false);
  },

  doSave: function () {

    if (this.get('isDateType')) {
      var d = Date.parse(this.get('date'));
      if (isNaN(d) || d < 0) {
        this.content.set('value', null);
      } else {
        this.content.set('value', d);
      }
    }

    if (this.get('isNumberType')) {
      if (isNaN(this.numberValue)) {
        this.content.set('value', null);
      } else {
        this.content.set('value', this.numberValue);
      }
    }
    FLOW.store.commit();
    this.set('inEditMode', false);

  },

  doValidateNumber: function () {
    // TODO should check for minus sign and decimal point, depending on question setting
    this.set('numberValue', this.get('numberValue').toString().replace(/[^\d.]/g, ""));
  }.observes('this.numberValue')
});

FLOW.QuestionAnswerOptionListView = Ember.CollectionView.extend({
  tagName: 'ul',
  content: null,
  itemViewClass: Ember.View.extend({
    template: Ember.Handlebars.compile("{{view.content.text}}")
  })
});

FLOW.QuestionAnswerInspectDataView = FLOW.QuestionAnswerView.extend({
  templateName: 'navData/question-answer',
});
<|endoftext|>


<commit_before>var config = require('yaml-config').readConfig(__dirname + '/../config/config.yml')
  , mail = require('../config/mail')(config)
  , emailTemplates = require('email-templates')
  , nodemailer = require('nodemailer');

// setup e-mail data with unicode symbols
var mailOptions = {
  from: "MyWebClass <mywebclass@webizly.com>", // sender address
  to: "MyWebClass <mywebclass@webizly.com>", // list of receivers
  subject: "Hello ", // Subject line
  text: "Hello world ", // plaintext body
  html: "<b>Hello world </b>" // html body
};

// send mail with templates
exports.sendMail = function (name, to, subject, data, callback) {

  emailTemplates(require('path').resolve(__dirname, '..', 'templates'), function (err, template) {

    if (err) {
      console.log(err);
    } else {

      template(name, data, function (err, html, text) {
        if (err) {
          console.log(err);
        } else {

          if (typeof to !== 'undefined') {
            mailOptions.to = to;
          }
          if (typeof subject !== 'undefined') {
            mailOptions.subject = subject;
          }
          mailOptions.html = html;
          mailOptions.text = text;
          mail.sendMail(mailOptions, function(error, response) {

            if (error) {
              console.log(error);
            } else {
              console.log("Message sent: " + response.message);
            }

            // if you don't want to use this transport object anymore, uncomment following line
            mail.close(); // shut down the connection pool, no more messages
            if (callback !== undefined) {
              callback(error, response);
            }
          });
        }

      });
    }
  });
};
<commit_msg>fixed to use jade template<commit_after> var config = require('yaml-config').readConfig(__dirname + '/../config/config.yml')
  , mail = require('../config/mail')(config)
  , emailTemplates = require('email-templates')
  , nodemailer = require('nodemailer');

// send mail with templates
exports.sendMail = function (name, to, subject, data, callback) {
  var template = 'welcome.jade';
  templateOptions = {
    user: {
      email: 'keith@webizly.com'
    }
  }
  jade.renderFile(path.join(__dirname, '../email_templates/account_activation', template), templateOptions , function(err, text) {
    if (err) {
      console.log(err);
    } else {
        var mailOptions = {
          from: "mywebclass@webizly.com", // sender address
          to: "keith@webizly.com", // list of receivers
          subject: "Hello", // Subject line
          text: "Hello world", // plaintext body
          html: text
        }
        if (typeof to !== 'undefined') {
           mailOptions.to = to;
         }
         if (typeof subject !== 'undefined') {
           mailOptions.subject = subject;
         }
        mail.sendMail(mailOptions, function(error, response) {
          if (error) {
            console.log(error);
          } else {
            console.log("Message sent: " + response.message);
          }
        });
        mail.close();
        if (callback !== undefined) {
          callback(error, response);
        }
      }
  });
}<|endoftext|>


<commit_before>var _ = require('underscore')
  , config = require('./config')
  , everypaas = require('everypaas')
  , nodemailer = require("nodemailer")
  , moment = require('moment')
  , logging = require('./logging')
  , jade = require('jade')
  , fs = require('fs')
  , path = require('path')
  , User = require('./models').User
  ;

/*
 * loading all of the email templates at server start
 */

function renderJade(filepath) {
  return jade.compile(fs.readFileSync(filepath, 'utf8'),{filename: filepath });
}

function renderUnderscore(filepath) {
  return _.template(fs.readFileSync(filepath, 'utf8'));
}

// TODO refactor

var invite_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext', 'invite.jade'));

var invite_html = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'html','invite.jade'));

var notify_password_change_html = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'html','notify_password_change.jade'));

var notify_password_change_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext','notify_password_change.jade'));

var notify_email_change_html = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'html','notify_email_change.jade'));

var notify_email_change_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext','notify_email_change.jade'));

var test_fail_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext','test_fail.jade'));

var test_succeed_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext','test_succeed.jade'));

var test_succeed_html = renderUnderscore(
            path.join(__dirname, '../views', 'email_templates', 'html','test_succeed.html'));

var test_fail_html = renderUnderscore(
            path.join(__dirname, '../views', 'email_templates', 'html','test_fail.html'));

var collaborator_invite_new_html = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'html','collaborator_invite_new_user.jade'));

var collaborator_invite_new_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext','collaborator_invite_new_user.jade'));

var collaborator_invite_existing_html = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'html','collaborator_invite_existing_user.jade'));

var collaborator_invite_existing_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext','collaborator_invite_existing_user.jade'));

/*
 * opening smtp connection
 */
// Default to printing a warning
var smtpTransport = {
  sendMail: function(opts, cb) {
    console.log("WARNING: no SMTP transport detected nor configured. Cannot send email.");
    cb(null, {message:null});
  }
};

// Try using SendGrid / Mailgun
if (everypaas.getSMTP() !== null) {
  console.log("Using SMTP transport: %j", everypaas.getSMTP());
  smtpTransport = nodemailer.createTransport.apply(null, everypaas.getSMTP());
} else {
  if (config.sendgrid) {
    console.log("Using Sendgrid transport from config ");
    smtpTransport = nodemailer.createTransport("SMTP",{
      service: "SendGrid",
      auth: {
          user: config.sendgrid.username,
          pass: config.sendgrid.password
      }
    });
  } else if (config.smtp) {
    console.log("Using SMTP transport from config");
    smtpTransport = nodemailer.createTransport("SMTP",{
      host:config.smtp.host,
      port:parseInt(config.smtp.port),
      auth: {
          user: config.smtp.auth.user,
          pass: config.smtp.auth.pass
      }
    });
  }
}

var send = exports.send = function(to, subject, text_body, html_body, from) {
  from = from || config.smtp ? config.smtp.from : null

  var mailOptions = {
    from: from, // sender address
    to: to, // list of receivers
    subject: subject, // Subject line
    text: text_body, // plaintext body_template
    html: html_body // html body
  }
  // send mail with defined transport object
  smtpTransport.sendMail(mailOptions, function(error, response){
      if(error){
          console.log("Error sending email:",error);
      }
    });

}

var elapsed_time = exports.elapsed_time = function(start,finish) {
  var in_seconds = (finish - start) / 1000;
  if (in_seconds>60)
  {
    return (Math.floor(in_seconds / 60) + "m " + Math.round(in_seconds % 60) + "s");
  } else {
    return (Math.round(in_seconds) + "s");
  }
}

exports.send_test_ok = function(job, project, to) {
  var display_name = project.display_name
  var subject = "[STRIDER] - " + display_name + " test success - " + job._id.toString().substr(0,8);
  var duration = elapsed_time(job.started.getTime(),job.finished.getTime());
  var url = config.server_name + "/" + display_name + "/job/" + job._id;

  var body_html;

  body_html = test_succeed_html(
    { display_name:display_name,
      finish_time:moment(job.finished_timestamp).format('YYYY-MM-DD h:mm a'),
      elapsed_time:duration,
      url:url,
      subject:subject,
      log_tail:format_stdmerged(job.std.merged, "html")
    }
  );

  var body_text = test_succeed_plaintext(
    { display_name:display_name,
      finish_time:job.finished,
      elapsed_time:duration,
      url:url,
      log_tail:format_stdmerged(job.std.merged, "plaintext")
    }
  );

  User.collaborators(project.name, function (err, users) {
    for (var i=0; i<users.length; i++) {
      send(users[i].email, subject, body_text, body_html)
    }
  })
}

exports.send_test_fail = function(job, project, to)
{
    var display_name = project.display_name
    var subject = "[STRIDER] - " + display_name + " test failure - " + job._id.toString().substr(0,8);

    var duration = elapsed_time(job.created.getTime(),job.finished.getTime());
    var url = config.server_name + "/" + display_name + "/job/" + job._id;
    console.debug("test url for email: " + url);

    var body_html;

    body_html = test_fail_html(
      { display_name:display_name,
        finish_time:moment(job.finished).format('YYYY-MM-DD h:mm a'),
        elapsed_time:duration,
        url:url,
        subject:subject,
        log_tail:format_stdmerged(job.std.merged, "html")
      }
    );

    var body_text = test_fail_plaintext(
      { display_name:display_name,
        finish_time:job.finished,
        elapsed_time:duration,
        url:url,
        log_tail:format_stdmerged(job.std.merged, "plaintext")
      }
    );

  User.collaborators(project.name, function (err, users) {
    for (var i=0; i<users.length; i++) {
      send(users[i].email, subject, body_text, body_html)
    }
  })
}

/*
 * format_stdmerged()
 *
 * Format the stdmerged property (test std stream output) for sendgrid consumption.
 * <stdmerged> - Job's stdmerged property.
 */
var format_stdmerged = exports.format_stdmerged = function (stdmerged,email_format)
{
  // 4k
  var start = stdmerged.length - 1 - 4096;
  if (start < 0) {
    start = 0;
  }
  var tlog = stdmerged.slice(start, stdmerged.length - 1).replace(/^\s+|\s+$/g,"");
  // Start each line with a space

  var tlines = tlog.split('\n');

  var b = new Buffer(8192);
  var offset = 0;
  _.each(tlines, function(l) {
    var towrite
    if (email_format === 'plaintext') {
      towrite = " " + l.replace(/\[(\d;)?\d*m/gi,'') + "\n";
    } else {
      towrite = " " + l.replace(/\[(\d;)?\d*m/gi,'') + "
\n";
    }

    b.write(towrite, offset, towrite.length);
    offset += towrite.length;
  });

  return b.toString('utf8', 0, offset);
}

exports.send_invite = function(code, email) {
    var subject = "Strider Invitation";

    var body_html = invite_html({ code:code, strider_server_name:config.server_name });
    var body_text = invite_plaintext({ code:code, strider_server_name:config.server_name });
    var to = email;

    send(to, subject, body_text, body_html);
}

exports.notify_password_change = function (user) {
  var current_time = new Date();
  var subject = "[STRIDER] - Password Change Notification - " +
                current_time.getHours() +
                ":" + current_time.getMinutes();
  var body_text = notify_password_change_plaintext();
  var body_html = notify_password_change_html();
  var to = user.email;

  send(to, subject, body_text,body_html);
}

exports.notify_email_change = function (user, old_email) {

  var current_time = new Date();
  var subject = "[STRIDER] - Email Address Change Notification - " + current_time.getHours() + ":" + current_time.getMinutes() ;

  var to = user.email;

  var body_text = notify_email_change_plaintext({old_email: old_email, user: user});
  var body_html = notify_email_change_html({old_email: old_email, user: user});

  send(to, subject, body_text,body_html);
  send(old_email,subject,body_text,body_html);

  console.log("send email change notification to " + old_email + " and " + to);
}

exports.send_invite_collaborator_new_user = function(inviter, email, code, url) {

  // not actually properly capitalized right now
  var display_name = url.replace(/^.*com\//gi, '');

  var subject = "[STRIDER] Invite to " + display_name;
  var to = email;

  var jade_variables = {
    inviter: inviter.email,
    display_name: display_name,
    code: code,
    strider_server_name: config.server_name
  };

  var body_text = collaborator_invite_new_plaintext(jade_variables);
  var body_html = collaborator_invite_new_html(jade_variables);

  send(to,subject,body_text,body_html);

  console.log("send collaborator invite to new user " + email + " for " +  display_name);
}

exports.send_invite_collaborator_existing_user = function(req, email, url) {

  // not actually properly capitalized right now
  var display_name = url.replace(/^.*com\//gi, '');

  var subject = "[STRIDER] Invite to " + display_name;
  var to = email;

  var jade_variables = {
    inviter: req.user.email,
    display_name: display_name,
    strider_server_name: config.server_name
  };

  var body_text = collaborator_invite_existing_plaintext(jade_variables);
  var body_html = collaborator_invite_existing_html(jade_variables);

  send(to,subject,body_text,body_html);

  console.log("send collaborator invite to existing user " + email + " for " +  display_name);
}

exports.notify_new_admin = function (user) {

  var current_time = new Date();
  var subject = "[STRIDER] - New Admin  - " + user + " - " + current_time.getHours() + ":" + current_time.getMinutes() ;
  var body_template = "Hello Core,\n\n" +
    "You have a new admin colleague: " + user + "\n\n" +
    "Hopefully this isn't unexpected.\n\n" +
    "Regards,\n" +
    " - StriderCD.com\n" +
    "   Brilliant Continuous Delivery";

    var body = body_template;

    var to = "core@beyondfog.com";
    send(to, subject, body, body);
    console.log("Sending admin notification for new admin " + user);
}
<commit_msg>Handle errors from User.collaborators<commit_after>var _ = require('underscore')
  , config = require('./config')
  , everypaas = require('everypaas')
  , nodemailer = require("nodemailer")
  , moment = require('moment')
  , logging = require('./logging')
  , jade = require('jade')
  , fs = require('fs')
  , path = require('path')
  , User = require('./models').User
  ;

/*
 * loading all of the email templates at server start
 */

function renderJade(filepath) {
  return jade.compile(fs.readFileSync(filepath, 'utf8'),{filename: filepath });
}

function renderUnderscore(filepath) {
  return _.template(fs.readFileSync(filepath, 'utf8'));
}

// TODO refactor

var invite_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext', 'invite.jade'));

var invite_html = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'html','invite.jade'));

var notify_password_change_html = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'html','notify_password_change.jade'));

var notify_password_change_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext','notify_password_change.jade'));

var notify_email_change_html = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'html','notify_email_change.jade'));

var notify_email_change_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext','notify_email_change.jade'));

var test_fail_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext','test_fail.jade'));

var test_succeed_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext','test_succeed.jade'));

var test_succeed_html = renderUnderscore(
            path.join(__dirname, '../views', 'email_templates', 'html','test_succeed.html'));

var test_fail_html = renderUnderscore(
            path.join(__dirname, '../views', 'email_templates', 'html','test_fail.html'));

var collaborator_invite_new_html = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'html','collaborator_invite_new_user.jade'));

var collaborator_invite_new_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext','collaborator_invite_new_user.jade'));

var collaborator_invite_existing_html = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'html','collaborator_invite_existing_user.jade'));

var collaborator_invite_existing_plaintext = renderJade(
            path.join(__dirname, '../views', 'email_templates', 'plaintext','collaborator_invite_existing_user.jade'));

/*
 * opening smtp connection
 */
// Default to printing a warning
var smtpTransport = {
  sendMail: function(opts, cb) {
    console.log("WARNING: no SMTP transport detected nor configured. Cannot send email.");
    cb(null, {message:null});
  }
};

// Try using SendGrid / Mailgun
if (everypaas.getSMTP() !== null) {
  console.log("Using SMTP transport: %j", everypaas.getSMTP());
  smtpTransport = nodemailer.createTransport.apply(null, everypaas.getSMTP());
} else {
  if (config.sendgrid) {
    console.log("Using Sendgrid transport from config ");
    smtpTransport = nodemailer.createTransport("SMTP",{
      service: "SendGrid",
      auth: {
          user: config.sendgrid.username,
          pass: config.sendgrid.password
      }
    });
  } else if (config.smtp) {
    console.log("Using SMTP transport from config");
    smtpTransport = nodemailer.createTransport("SMTP",{
      host:config.smtp.host,
      port:parseInt(config.smtp.port),
      auth: {
          user: config.smtp.auth.user,
          pass: config.smtp.auth.pass
      }
    });
  }
}

var send = exports.send = function(to, subject, text_body, html_body, from) {
  from = from || config.smtp ? config.smtp.from : null

  var mailOptions = {
    from: from, // sender address
    to: to, // list of receivers
    subject: subject, // Subject line
    text: text_body, // plaintext body_template
    html: html_body // html body
  }
  // send mail with defined transport object
  smtpTransport.sendMail(mailOptions, function(error, response){
      if(error){
          console.log("Error sending email:",error);
      }
    });

}

var elapsed_time = exports.elapsed_time = function(start,finish) {
  var in_seconds = (finish - start) / 1000;
  if (in_seconds>60)
  {
    return (Math.floor(in_seconds / 60) + "m " + Math.round(in_seconds % 60) + "s");
  } else {
    return (Math.round(in_seconds) + "s");
  }
}

exports.send_test_ok = function(job, project, to) {
  var display_name = project.display_name
  var subject = "[STRIDER] - " + display_name + " test success - " + job._id.toString().substr(0,8);
  var duration = elapsed_time(job.started.getTime(),job.finished.getTime());
  var url = config.server_name + "/" + display_name + "/job/" + job._id;

  var body_html;

  body_html = test_succeed_html(
    { display_name:display_name,
      finish_time:moment(job.finished_timestamp).format('YYYY-MM-DD h:mm a'),
      elapsed_time:duration,
      url:url,
      subject:subject,
      log_tail:format_stdmerged(job.std.merged, "html")
    }
  );

  var body_text = test_succeed_plaintext(
    { display_name:display_name,
      finish_time:job.finished,
      elapsed_time:duration,
      url:url,
      log_tail:format_stdmerged(job.std.merged, "plaintext")
    }
  );

  User.collaborators(project.name, function (err, users) {
    if (err) console.error('[email-ok] Error finding collaborators for project', err.message)
    for (var i=0; i<users.length; i++) {
      send(users[i].email, subject, body_text, body_html)
    }
  })
}

exports.send_test_fail = function(job, project, to)
{
    var display_name = project.display_name
    var subject = "[STRIDER] - " + display_name + " test failure - " + job._id.toString().substr(0,8);

    var duration = elapsed_time(job.created.getTime(),job.finished.getTime());
    var url = config.server_name + "/" + display_name + "/job/" + job._id;
    console.debug("test url for email: " + url);

    var body_html;

    body_html = test_fail_html(
      { display_name:display_name,
        finish_time:moment(job.finished).format('YYYY-MM-DD h:mm a'),
        elapsed_time:duration,
        url:url,
        subject:subject,
        log_tail:format_stdmerged(job.std.merged, "html")
      }
    );

    var body_text = test_fail_plaintext(
      { display_name:display_name,
        finish_time:job.finished,
        elapsed_time:duration,
        url:url,
        log_tail:format_stdmerged(job.std.merged, "plaintext")
      }
    );

  User.collaborators(project.name, function (err, users) {
    if (err) console.error('[email-fail] Error finding collaborators for project', err.message)
    for (var i=0; i<users.length; i++) {
      send(users[i].email, subject, body_text, body_html)
    }
  })
}

/*
 * format_stdmerged()
 *
 * Format the stdmerged property (test std stream output) for sendgrid consumption.
 * <stdmerged> - Job's stdmerged property.
 */
var format_stdmerged = exports.format_stdmerged = function (stdmerged,email_format)
{
  // 4k
  var start = stdmerged.length - 1 - 4096;
  if (start < 0) {
    start = 0;
  }
  var tlog = stdmerged.slice(start, stdmerged.length - 1).replace(/^\s+|\s+$/g,"");
  // Start each line with a space

  var tlines = tlog.split('\n');

  var b = new Buffer(8192);
  var offset = 0;
  _.each(tlines, function(l) {
    var towrite
    if (email_format === 'plaintext') {
      towrite = " " + l.replace(/\[(\d;)?\d*m/gi,'') + "\n";
    } else {
      towrite = " " + l.replace(/\[(\d;)?\d*m/gi,'') + "
\n";
    }

    b.write(towrite, offset, towrite.length);
    offset += towrite.length;
  });

  return b.toString('utf8', 0, offset);
}

exports.send_invite = function(code, email) {
    var subject = "Strider Invitation";

    var body_html = invite_html({ code:code, strider_server_name:config.server_name });
    var body_text = invite_plaintext({ code:code, strider_server_name:config.server_name });
    var to = email;

    send(to, subject, body_text, body_html);
}

exports.notify_password_change = function (user) {
  var current_time = new Date();
  var subject = "[STRIDER] - Password Change Notification - " +
                current_time.getHours() +
                ":" + current_time.getMinutes();
  var body_text = notify_password_change_plaintext();
  var body_html = notify_password_change_html();
  var to = user.email;

  send(to, subject, body_text,body_html);
}

exports.notify_email_change = function (user, old_email) {

  var current_time = new Date();
  var subject = "[STRIDER] - Email Address Change Notification - " + current_time.getHours() + ":" + current_time.getMinutes() ;

  var to = user.email;

  var body_text = notify_email_change_plaintext({old_email: old_email, user: user});
  var body_html = notify_email_change_html({old_email: old_email, user: user});

  send(to, subject, body_text,body_html);
  send(old_email,subject,body_text,body_html);

  console.log("send email change notification to " + old_email + " and " + to);
}

exports.send_invite_collaborator_new_user = function(inviter, email, code, url) {

  // not actually properly capitalized right now
  var display_name = url.replace(/^.*com\//gi, '');

  var subject = "[STRIDER] Invite to " + display_name;
  var to = email;

  var jade_variables = {
    inviter: inviter.email,
    display_name: display_name,
    code: code,
    strider_server_name: config.server_name
  };

  var body_text = collaborator_invite_new_plaintext(jade_variables);
  var body_html = collaborator_invite_new_html(jade_variables);

  send(to,subject,body_text,body_html);

  console.log("send collaborator invite to new user " + email + " for " +  display_name);
}

exports.send_invite_collaborator_existing_user = function(req, email, url) {

  // not actually properly capitalized right now
  var display_name = url.replace(/^.*com\//gi, '');

  var subject = "[STRIDER] Invite to " + display_name;
  var to = email;

  var jade_variables = {
    inviter: req.user.email,
    display_name: display_name,
    strider_server_name: config.server_name
  };

  var body_text = collaborator_invite_existing_plaintext(jade_variables);
  var body_html = collaborator_invite_existing_html(jade_variables);

  send(to,subject,body_text,body_html);

  console.log("send collaborator invite to existing user " + email + " for " +  display_name);
}

exports.notify_new_admin = function (user) {

  var current_time = new Date();
  var subject = "[STRIDER] - New Admin  - " + user + " - " + current_time.getHours() + ":" + current_time.getMinutes() ;
  var body_template = "Hello Core,\n\n" +
    "You have a new admin colleague: " + user + "\n\n" +
    "Hopefully this isn't unexpected.\n\n" +
    "Regards,\n" +
    " - StriderCD.com\n" +
    "   Brilliant Continuous Delivery";

    var body = body_template;

    var to = "core@beyondfog.com";
    send(to, subject, body, body);
    console.log("Sending admin notification for new admin " + user);
}
<|endoftext|>


<commit_before>"use strict";

var querystring = require('querystring');
var https = require('https');
var _ = require('underscore');
var fs = require('fs');
var SmtpapiHeaders = require('./smtpapi_headers');

function Email(params) {
  /*
   * Default parameters for sending mail
   */
  var default_mail_params = {
    to: [],
    from: '',
    smtpapi: new SmtpapiHeaders(),
    subject: '',
    text: '',
    html: '',
    bcc: [],
    replyto: '',
    date: new Date(),
    files: {},
    file_data: {},
    headers: {}
  };

  _.extend(this, default_mail_params, params);
}

/*
 * Validates an email. This is used before sending, but
 * can still be invoked programatically
 *
 * @return {Boolean} The result of the validation
 */
Email.prototype.validate = function() {
  //TODO: add validation
}

Email.prototype.setHeaders = function(val) {
  if (_.isObject(val)) {
    this.headers = val;
  }
}

Email.prototype.addHeaders = function(val){
  if (_.isObject(val)) {
    _.extend(this.headers, val);
  }
}

Email.prototype.addTo = function(to) {
  this.smtpapi.addTo(to);
}

Email.prototype.addSubVal = function(key, val) {
  this.smtpapi.addSubVal(key, val);
}

Email.prototype.setUniqueArgs = function(val) {
  this.smtpapi.setUniqueArgs(val);
}

Email.prototype.addUniqueArgs = function (val) {
  this.smtpapi.addUniqueArgs(val);
}

Email.prototype.setCategory = function(val) {
  this.smtpapi.setCategory(val);
}

Email.prototype.addCategory = function(val) {
  this.smtpapi.addCategory(val);
}

Email.prototype.setSection = function(val) {
  this.smtpapi.setSection(val);
}

Email.prototype.addSection = function(val) {
  this.smtpapi.addSection(val);
}

Email.prototype.addFilterSetting = function(filter, setting, val) {
  this.smtpapi.addFilterSetting(filter, setting, val);
}

Email.prototype.setFilterSetting = function(filters) {
  this.smtpapi.setFilterSetting(filters);
}

Email.prototype.addFile = function(filename, filepath) {
  this.files[filename] = filepath;
}

Email.prototype.processFiles = function(callback) {
  var self = this;
  var attachments_count = _.size(this.files);
  _(this.files).each(function(v, k) {
    fs.readFile(v, function(error, data) {
      attachments_count--;
      if (error) {
        return callback(false, error);
      } else {
        self.file_data[k] = data;
      }

      if (attachments_count == 0) {
        callback(true);
      }
    });
  });
}

Email.prototype.toWebFormat = function() {
  var data = {
    to: this.to,
    from: this.from,
    'x-smtpapi': this.smtpapi.toJson(),
    subject: this.subject,
    text: this.text,
    html: this.html,
    bcc: this.bcc,
    replyto: this.replyto,
    headers: JSON.stringify(this.headers)
  };

  this.updateMissingTo(data);

  return data;
}

Email.prototype.toSmtpFormat = function() {
  var data = {
    to: this.to,
    sender: this.from,
    subject: this.subject,
    body: this.text,
    html: this.html,
    reply_to: this.replyto,
    headers: this.headers
  };
  data.headers['x-smtpapi'] = this.smtpapi.toJson();

  if (_.size(this.file_data) > 0) {
    var attachments = [];
    _(this.file_data).each(function(v, k) {
      attachments.push({filename: k, contents: v});
    });
    data.attachments = attachments;
  }

  this.updateMissingTo(data);

  return data;
}

/*
 * There needs to be at least 1 to address, or else the mail won't send.
 * This method modifies the data that will be sent via either Rest or SMTP.
 *
 * @param  {object}  data  The data parameter to send via Rest or SMTP
 */
Email.prototype.updateMissingTo = function(data) {
  if (_.isEmpty(data.to) && this.smtpapi.to && !_.isEmpty(this.smtpapi.to)) {
    data.to = this.from;
  }
}

Email.prototype.hasFiles = function() {
  return _(this.files).size() > 0;
}

module.exports = Email;
<commit_msg>initial changes to files<commit_after>"use strict";

var querystring = require('querystring');
var https = require('https');
var http = require('http');
var _ = require('underscore');
var fs = require('fs');
var SmtpapiHeaders = require('./smtpapi_headers');

function Email(params) {
  /*
   * Default parameters for sending mail
   */
  var default_mail_params = {
    to: [],
    from: '',
    smtpapi: new SmtpapiHeaders(),
    subject: '',
    text: '',
    html: '',
    bcc: [],
    replyto: '',
    date: new Date(),
    files: [],
    headers: {}
  };

  _.extend(this, default_mail_params, params);
}

/*
 * Validates an email. This is used before sending, but
 * can still be invoked programatically
 *
 * @return {Boolean} The result of the validation
 */
Email.prototype.validate = function() {
  //TODO: add validation
}

Email.prototype.setHeaders = function(val) {
  if (_.isObject(val)) {
    this.headers = val;
  }
}

Email.prototype.addHeaders = function(val){
  if (_.isObject(val)) {
    _.extend(this.headers, val);
  }
}

Email.prototype.addTo = function(to) {
  this.smtpapi.addTo(to);
}

Email.prototype.addSubVal = function(key, val) {
  this.smtpapi.addSubVal(key, val);
}

Email.prototype.setUniqueArgs = function(val) {
  this.smtpapi.setUniqueArgs(val);
}

Email.prototype.addUniqueArgs = function (val) {
  this.smtpapi.addUniqueArgs(val);
}

Email.prototype.setCategory = function(val) {
  this.smtpapi.setCategory(val);
}

Email.prototype.addCategory = function(val) {
  this.smtpapi.addCategory(val);
}

Email.prototype.setSection = function(val) {
  this.smtpapi.setSection(val);
}

Email.prototype.addSection = function(val) {
  this.smtpapi.addSection(val);
}

Email.prototype.addFilterSetting = function(filter, setting, val) {
  this.smtpapi.addFilterSetting(filter, setting, val);
}

Email.prototype.setFilterSetting = function(filters) {
  this.smtpapi.setFilterSetting(filters);
}

Email.prototype.addFile = function(file_object) {
  var file = file_object;
  if (!_.isUndefined(file.content)) {
    file.type = 'content';
  } else if (!_.isUndefined(file.path)) {
    file.type = 'path';
    if (_.isEmtpy(file.contentType)) {
      file.contentType = mime.lookup(file.path);
    }
  } else if (!_.isUndefined(file.url)) {
    file.type = 'url';
    if (_.isEmtpy(file.contentType)) {
      file.contentType = mime.lookup(file.url);
  } else {
    file.type = 'none';
    file.contentType = 'none';
  }
  this.files.push(file);
}

Email.prototype.processFiles = function(callback) {
  var self = this;
  var attachments_count = _.size(this.files);
  _(this.files).each(function(file) {
    if (file.type === 'path') {
      fs.readFile(file, function(error, data) {
        attachments_count--;
        if (error) {
          return callback(false, error);
        } else {
          file.content = data;
        }
      });
    } else if (file.type === 'url') {
      var urlParts = url.parse(file.url);
      var host = urlParts.host;
      var path = urlParts.path;
      var req = https.request({host: host, path: path, method: 'GET'}, function(res) {
        attachments_count--;
        var bufferArray = [];
        var totalBufferSize = 0;
        res.setEncoding('base64');
        res.on('data', function(chunk) {
          totalBufferSize += Buffer.byteLength(chunk);
          bufferArray.push(chunk);
        }).on('end', function() {
          var buffer = new Buffer(totalBufferSize, 'base64');
          var bufPos = 0;
          _(bufferArray).each(function(val) {
            bufPos += buffer.write(val, bufPos, 'base64');
          });
          file.content = buffer;
        });
      }).on('error', function(error) {
        return callback(false, error);
      });
    } else if (file.type === 'content') {
      attachments_count--;
      // nothing else to do, right?
    } else if (file.type === 'none') {
      attachments_count--;
      // nothing to see but us chickens, don't punch us.
    }
    if (attachments_count == 0) {
      callback(true);
    } else {
      callback(false);
    }
  });
}

Email.prototype.toWebFormat = function() {
  var data = {
    to: this.to,
    from: this.from,
    'x-smtpapi': this.smtpapi.toJson(),
    subject: this.subject,
    text: this.text,
    html: this.html,
    bcc: this.bcc,
    replyto: this.replyto,
    headers: JSON.stringify(this.headers)
  };

  this.updateMissingTo(data);

  return data;
}

Email.prototype.toSmtpFormat = function() {
  var data = {
    to: this.to,
    sender: this.from,
    subject: this.subject,
    body: this.text,
    html: this.html,
    reply_to: this.replyto,
    headers: this.headers
  };
  data.headers['x-smtpapi'] = this.smtpapi.toJson();

  if (_.size(this.file_data) > 0) {
    var attachments = [];
    _(this.file_data).each(function(v, k) {
      attachments.push({filename: k, contents: v});
    });
    data.attachments = attachments;
  }

  this.updateMissingTo(data);

  return data;
}

/*
 * There needs to be at least 1 to address, or else the mail won't send.
 * This method modifies the data that will be sent via either Rest or SMTP.
 *
 * @param  {object}  data  The data parameter to send via Rest or SMTP
 */
Email.prototype.updateMissingTo = function(data) {
  if (_.isEmpty(data.to) && this.smtpapi.to && !_.isEmpty(this.smtpapi.to)) {
    data.to = this.from;
  }
}

Email.prototype.hasFiles = function() {
  return _(this.files).size() > 0;
}

module.exports = Email;
<|endoftext|>


<commit_before>'use strict';

var async = require('async');
var EventEmitter = require('events').EventEmitter;
var glob = require('glob');
var path = require('path');
var util = require('util');
var _ = require('lodash');

/**
 * Temporary logger function.
 */
function log() {
    console.log.apply(this, arguments);
}

/**
 * Variable debugger.
 *
 * @private
 * @param {*} obj - Variable to debug.
 */
function debug(obj) {
    console.log(util.inspect(obj, { depth: null, colors: true }));
}

/**
 * Convenience function to deeply iterate an object.
 *
 * @private
 * @param {object|string} item - The item to iterate.
 * @param {function} callback - The callback function.
 */
function iterate(item, callback) {
    Object.keys(item).forEach(function (key) {
        if (typeof item[key] === 'object') {
            return iterate(item[key], callback);
        } else {
            callback(item, key);
        }
    });
}

/**
 * The class constructor.
 *
 * @param {object} [api] - An optional API object.
 * @cosnstructor
 */
function Emese(api) {
    this.api = api || {};
    this.tasks = [];
}

util.inherits(Emese, EventEmitter);

/**
 * Adds a tree of resources. Your API will be extended with that same structure, with the resources being loaded as
 * values of the properties described.
 *
 * @todo Make the resources detect exports containing a propetry like "task" or "lifecycle" to automatically incorporate
 * it in the tasks
 * @example
 * var loader = new Emese();
 * loader.resources({
 *     client: {
 *         rest: 'test/client/rest/*.js',
 *         soap: 'test/client/soap/*.js'
 *     },
 *     common: 'test/common/*.js',
 *     plugin: 'test/plugin/*.js',
 *     service: 'test/service/*.js',
 *     transformer: 'test/transformer/*.js'
 * });
 * @param {object} resources - A resource tree.
 * @returns {Emese}
 */
Emese.prototype.resources = function (resources) {
    this.resources = resources;
    return this;
};

/**
 * Add one or more tasks to the loading process.
 *
 * @todo Make tasks objects with options like the function itself and a weight to sort them
 * @param {function|function[]} task - One or more tasks. The functions must have a callback as first argument.
 * @returns {Emese}
 */
Emese.prototype.addTask = function (task) {
    if (_.isArray(task)) {
        this.tasks = this.tasks.concat(task);
    } else {
        this.tasks.push(task);
    }
    return this;
};

/**
 * Loads the app:
 * 1) Resources are loaded in parallel
 * 2) Tasks are executed sequentially
 *
 * @param {function} [done] - Optional callback function.
 * @fires Emese#done
 * @fires Emese#error
 * @returns {Emese}
 */
Emese.prototype.load = function (done) {
    var self = this;
    var resourceLoaders = [];
    var cwd = process.cwd();
    iterate(this.resources, function (item, key) {
        resourceLoaders.push(function (next) {
            glob(item[key], {}, function (err, files) {
                if (err) {
                    return next(err)
                }
                item[key] = {};
                files.forEach(function (file) {
                    var basename = path.basename(file, path.extname(file));
                    item[key][basename] = require(path.join(cwd, file));
                    log('Loaded \'' + file + '\'');
                });
                next();
            });
        });
    });

    async.series([
        function loadResources(next) {
            async.parallel(resourceLoaders, function (err) {
                if (err) {
                    return next(err);
                }

                _.extend(self.api, self.resources);
                next();
            });
        },
        function runTasks(next) {
            async.series(self.tasks, function (err) {
                if (err) {
                    return next(err);
                }
                next();
            });
        }
    ], function (err) {
        if (err) {
            self.emit('error', err);
            return done && done(err);
        }
        self.emit('done', self.api);
        if (done) {
            done(null, self.api);
        }
    });

    return this;
};

/**
 * An error event while loading.
 *
 * @event Emese#error
 * @type {Error}
 */

/**
 * Event fired when the API has been loaded.
 *
 * @event Emese#done
 * @type {object}
 */

var api = {
    version: '0.0.1',
    log: log
};

var loader = new Emese(api);

loader.resources({
    client: {
        rest: 'test/client/rest/*.js',
        soap: 'test/client/soap/*.js'
    },
    public: 'test/public/**/*.json',
    common: 'test/common/*.js',
    plugin: 'test/plugin/*.js',
    service: 'test/service/*.js',
    transformer: 'test/transformer/*.js'
});

loader.addTask([
    function (next) {
        log('Opening database connection');
        setTimeout(function () {
            log('Connected to the database');
            next();
        }, 150);
    },
    function (next) {
        log('Initializing cache');
        setTimeout(function () {
            log('Cache initialized');
            next();
        }, 200);
    },
    function (next) {
        api.plugin.server.load(next);
    }
]);

loader.load();

loader.on('error', function (err) {
    log('ERROR!');
    log(err);
});

loader.on('done', function (api) {
    log('Your application is ready to serve');
    debug(api);
});

<commit_msg>Converted resource loading into a new task, so you can mix them with actual tasks<commit_after>'use strict';

var async = require('async');
var EventEmitter = require('events').EventEmitter;
var glob = require('glob');
var path = require('path');
var util = require('util');
var _ = require('lodash');

/**
 * Temporary logger function.
 */
function log() {
    console.log.apply(this, arguments);
}

/**
 * Variable debugger.
 *
 * @private
 * @param {*} obj - Variable to debug.
 */
function debug(obj) {
    console.log(util.inspect(obj, { depth: null, colors: true }));
}

/**
 * Convenience function to deeply iterate an object.
 *
 * @private
 * @param {object|string} item - The item to iterate.
 * @param {function} callback - The callback function.
 */
function iterate(item, callback) {
    Object.keys(item).forEach(function (key) {
        if (typeof item[key] === 'object') {
            return iterate(item[key], callback);
        } else {
            callback(item, key);
        }
    });
}

/**
 * The class constructor.
 *
 * @param {object} [api] - An optional API object.
 * @cosnstructor
 */
function Emese(api) {
    this.api = api || {};
    this.tasks = [];
}

util.inherits(Emese, EventEmitter);

/**
 * Adds a tree of resources. Your API will be extended with that same structure, with the resources being loaded as
 * values of the properties described.
 *
 * @todo Make the resources detect exports containing a propetry like "task" or "lifecycle" to automatically incorporate
 * it in the tasks
 * @example
 * var loader = new Emese();
 * loader.resources({
 *     client: {
 *         rest: 'test/client/rest/*.js',
 *         soap: 'test/client/soap/*.js'
 *     },
 *     common: 'test/common/*.js',
 *     plugin: 'test/plugin/*.js',
 *     service: 'test/service/*.js',
 *     transformer: 'test/transformer/*.js'
 * });
 * @param {object} resources - A resource tree.
 * @returns {Emese}
 */
Emese.prototype.resources = function (resources) {
    var self = this;
    this.tasks.push(function (next) {

        var resourceLoaders = [];
        var cwd = process.cwd();

        iterate(resources, function (item, key) {
            resourceLoaders.push(function (next) {
                glob(item[key], {}, function (err, files) {
                    if (err) {
                        return next(err)
                    }
                    item[key] = {};
                    files.forEach(function (file) {
                        var basename = path.basename(file, path.extname(file));
                        item[key][basename] = require(path.join(cwd, file));
                        log('Loaded \'' + file + '\'');
                    });
                    next();
                });
            });
        });

        async.parallel(resourceLoaders, function (err) {
            if (err) {
                return next(err);
            }
            // TODO: Replace extend with something else
            _.extend(self.api, resources);
            next();
        });

    });
    return this;
};

/**
 * Add one or more tasks to the loading process.
 *
 * @todo Make tasks objects with options like the function itself and a weight to sort them
 * @param {function|function[]} task - One or more tasks. The functions must have a callback as first argument.
 * @returns {Emese}
 */
Emese.prototype.addTask = function (task) {
    if (Array.isArray(task)) {
        this.tasks = this.tasks.concat(task);
    } else {
        this.tasks.push(task);
    }
    return this;
};

/**
 * Loads the app:
 * 1) Resources are loaded in parallel
 * 2) Tasks are executed sequentially
 *
 * @param {function} [done] - Optional callback function.
 * @fires Emese#done
 * @fires Emese#error
 * @returns {Emese}
 */
Emese.prototype.load = function (done) {
    var self = this;

    async.series(self.tasks, function (err) {
        if (err) {
            self.emit('error', err);
            return done && done(err);
        }
        self.emit('done', self.api);
        if (done) {
            done(null, self.api);
        }
    });

    return this;
};

/**
 * An error event while loading.
 *
 * @event Emese#error
 * @type {Error}
 */

/**
 * Event fired when the API has been loaded.
 *
 * @event Emese#done
 * @type {object}
 */

var api = {
    version: '0.0.1',
    log: log
};

var loader = new Emese(api);

loader.resources({
    client: {
        rest: 'test/client/rest/*.js',
        soap: 'test/client/soap/*.js'
    },
    common: 'test/common/*.js',
    plugin: 'test/plugin/*.js',
    service: 'test/service/*.js',
    transformer: 'test/transformer/*.js'
});

loader.addTask([
    function (next) {
        log('Opening database connection');
        setTimeout(function () {
            log('Connected to the database');
            next();
        }, 150);
    },
    function (next) {
        log('Initializing cache');
        setTimeout(function () {
            log('Cache initialized');
            next();
        }, 200);
    },
    function (next) {
        api.plugin.server.load(next);
    }
]);

loader.resources({
    public: 'test/public/**/*.json'
});

loader.load();

loader.on('error', function (err) {
    log('ERROR!');
    log(err);
});

loader.on('done', function (api) {
    log('Your application is ready to serve');
    debug(api);
});

<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash'),
    fp = require('lodash/fp'),
    os = require('os'),
    Alias = require('./alias.js'),
    util = require('./util.js');

/*----------------------------------------------------------------------------*/

function getTag(entry, tagName) {
  var parsed = entry.parsed;
  return _.find(parsed.tags, ['title', tagName]);
}

function getValue(entry, tagName) {
  var parsed = entry.parsed,
      result = parsed.description,
      tag = getTag(entry, tagName);

  if (tagName == 'type') {
    result = _.get(tag, 'type.name');
  } else if (tagName != 'description') {
    result = _.get(tag, 'name') || _.get(tag, 'description');
  }
  return tagName == 'example'
    ? _.toString(result)
    : util.format(result);
}

function hasTag(entry, tagName) {
  return getTag(entry, tagName) !== undefined;
}

function reduceParamType(tag) {
  var expression = tag.expression,
      result = '',
      type = tag.type;

  if (type == 'AllLiteral') {
    result = '*';
  }
  if (type == 'NameExpression') {
    result = tag.name;
  }
  if (type == 'RestType') {
    result = '...' + result;
  }
  if (type == 'TypeApplication') {
    expression = undefined;
    result = _(tag)
      .chain()
      .get('applications')
      .map(_.flow(reduceParamType, fp.add(fp, '[]')))
      .sort(util.compareNatural)
      .join('|')
      .value();
  }
  if (type == 'UnionType') {
    result = _(tag)
      .chain()
      .get('elements')
      .map(reduceParamType)
      .sort(util.compareNatural)
      .join('|')
      .value();
  }
  if (expression) {
    result += reduceParamType(expression);
  }
  return type == 'UnionType'
    ? ('(' + result + ')')
    : result;
}

/*----------------------------------------------------------------------------*/

/**
 * The Entry constructor.
 *
 * @constructor
 * @param {string} entry The documentation entry to analyse.
 * @param {string} source The source code.
 * @param {string} [lang='js'] The language highlighter used for code examples.
 */
function Entry(entry, source, lang) {
  this.parsed = util.parse(
    entry
      .replace(/@param-/g, '@paramhidden')
      .replace(/(\*)\/\s*.+$/, '*')
  );

  this.entry = entry;
  this.lang = lang == null ? 'js' : lang;
  this.source = source.replace(os.EOL, '\n');
  this.getCall = _.memoize(this.getCall);
  this.getCategory = _.memoize(this.getCategory);
  this.getDesc = _.memoize(this.getDesc);
  this.getExample = _.memoize(this.getExample);
  this.getHash = _.memoize(this.getHash);
  this.getLineNumber = _.memoize(this.getLineNumber);
  this.getName = _.memoize(this.getName);
  this.getReturns = _.memoize(this.getReturns);
  this.getSince = _.memoize(this.getSince);
  this.getType = _.memoize(this.getType);
  this.isAlias = _.memoize(this.isAlias);
  this.isCtor = _.memoize(this.isCtor);
  this.isFunction = _.memoize(this.isFunction);
  this.isLicense = _.memoize(this.isLicense);
  this.isPlugin = _.memoize(this.isPlugin);
  this.isPrivate = _.memoize(this.isPrivate);
  this.isStatic = _.memoize(this.isStatic);
  this._aliases = this._members = this._params = undefined;
}

/**
 * Extracts the documentation entries from source code.
 *
 * @static
 * @memberOf Entry
 * @param {string} source The source code.
 * @returns {Array} The array of entries.
 */
function getEntries(source) {
  source = _.toString(source);
  return source.match(/\/\*\*(?![-!])[\s\S]*?\*\/\s*.+/g) || [];
}

/**
 * Extracts the entry's `alias` objects.
 *
 * @memberOf Entry
 * @param {number} index The index of the array value to return.
 * @returns {Array|string} The entry's `alias` objects.
 */
function getAliases(index) {
  if (this._aliases === undefined) {
    var owner = this;
    this._aliases = _(getValue(this, 'alias'))
      .split(/,\s*/)
      .compact()
      .sort(util.compareNatural)
      .map(function(value) { return new Alias(value, owner); })
      .value();
  }
  var result = this._aliases;
  return index === undefined ? result : result[index];
}

/**
 * Extracts the function call from the entry.
 *
 * @memberOf Entry
 * @returns {string} The function call.
 */
function getCall() {
  var result = /\*\/\s*(?:function\s+([^(]*)|(.*?)(?=[:=,]))/.exec(this.entry);
  if (result) {
    result = (result[1] || result[2]).split('.').pop();
    result = _.trim(_.trim(result), "'").split('var ').pop();
    result = _.trim(result);
  }
  var name = getValue(this, 'name') || result || '';
  if (!this.isFunction()) {
    return name;
  }
  var params = this.getParams();
  result = _.castArray(result);

  // Compile the function call syntax.
  _.each(params, function(param) {
    var paramValue = param[1],
        parentParam = _.result(/\w+(?=\.[\w.]+)/.exec(paramValue), 0);

    var parentIndex = parentParam == null ? -1 : _.findIndex(params, function(param) {
      return _.trim(param[1], '[]') == parentParam;
    });

    // Skip params that are properties of other params (e.g. `options.leading`).
    if (_.result(params[parentIndex], 0) != 'Object') {
      result.push(paramValue);
    }
  });

  // Format the function call.
  return name + '(' + result.slice(1).join(', ') + ')';
}

/**
 * Extracts the entry's `category` data.
 *
 * @memberOf Entry
 * @returns {string} The entry's `category` data.
 */
function getCategory() {
  var result = getValue(this, 'category');
  return result || (this.getType() == 'Function' ? 'Methods' : 'Properties');
}

/**
 * Extracts the entry's description.
 *
 * @memberOf Entry
 * @returns {string} The entry's description.
 */
function getDesc() {
  var type = this.getType(),
      result = getValue(this, 'description');

  return (!result || type == 'Function' || type == 'unknown')
    ? result
    : ('(' + _.trim(type.replace(/\|/g, ', '), '()') + '): ' + result);
}

/**
 * Extracts the entry's `example` data.
 *
 * @memberOf Entry
 * @returns {string} The entry's `example` data.
 */
function getExample() {
  var result = getValue(this, 'example');
  return result && ('```' + this.lang + '\n' + result + '\n```');
}

/**
 * Extracts the entry's hash value for permalinking.
 *
 * @memberOf Entry
 * @param {string} [style] The hash style.
 * @returns {string} The entry's hash value (without a hash itself).
 */
function getHash(style) {
  var result = this.getMembers(0) || '';

  switch (style) {
    case 'github':
      if (result) {
        result += this.isPlugin() ? 'prototype' : '';
      }
      result += this.getCall();
      return result
        .replace(/\(\[|\[\]/g, '')
        .replace(/[\t =|\'"{}.()\]]/g, '')
        .replace(/[\[#,]+/g, '-')
        .toLowerCase();

    case 'default':
    default:
      if (result) {
        result += '-' + (this.isPlugin() ? 'prototype-' : '');
      }
      result += this.isAlias() ? this.getOwner().getName() : this.getName();
      return result
        .replace(/\./g, '-')
        .replace(/^_-/, '');
  }
}

/**
 * Resolves the entry's line number.
 *
 * @memberOf Entry
 * @returns {number} The entry's line number.
 */
function getLineNumber() {
  var lines = this.source
    .slice(0, this.source.indexOf(this.entry) + this.entry.length)
    .match(/\n/g)
    .slice(1);

  // Offset by 2 because the first line number is before a line break and the
  // last line doesn't include a line break.
  return lines.length + 2;
}

/**
 * Extracts the entry's `member` data.
 *
 * @memberOf Entry
 * @param {number} [index] The index of the array value to return.
 * @returns {Array|string} The entry's `member` data.
 */
function getMembers(index) {
  if (this._members === undefined) {
    this._members = _(getValue(this, 'member') || getValue(this, 'memberOf'))
      .split(/,\s*/)
      .compact()
      .sort(util.compareNatural)
      .value();
  }
  var result = this._members;
  return index === undefined ? result : result[index];
}

/**
 * Extracts the entry's `name` data.
 *
 * @memberOf Entry
 * @returns {string} The entry's `name` data.
 */
function getName() {
  return hasTag(this, 'name')
    ? getValue(this, 'name')
    : _.toString(_.first(this.getCall().split('(')));
}

/**
 * Extracts the entry's `param` data.
 *
 * @memberOf Entry
 * @param {number} [index] The index of the array value to return.
 * @returns {Array} The entry's `param` data.
 */
function getParams(index) {
  if (this._params === undefined) {
    this._params = _.map(_.filter(this.parsed.tags, ['title', 'param']), function(tag) {
      var defaultValue = tag['default'],
          desc = util.format(tag.description),
          name = tag.name || '',
          type = reduceParamType(tag.type);

      if (defaultValue != null) {
        name += '=' + defaultValue;
      }
      if (_.get(tag, 'type.type') == 'OptionalType') {
        name = '[' + name + ']';
      }
      return [type, name,  desc];
    });
  }
  var result = this._params;
  return index === undefined ? result : result[index];
}

/**
 * Extracts the entry's `returns` data.
 *
 * @memberOf Entry
 * @returns {array} The entry's `returns` data.
 */
function getReturns() {
  var tag = getTag(this, 'returns'),
      desc = _.get(tag, 'description') || '',
      type = _.get(tag, 'type.name') || '*';

  return tag ? [type, desc] : [];
}

/**
 * Extracts the entry's `since` data.
 *
 * @memberOf Entry
 * @returns {string} The entry's `since` data.
 */
function getSince() {
  return getValue(this, 'since');
}

/**
 * Extracts the entry's `type` data.
 *
 * @memberOf Entry
 * @returns {string} The entry's `type` data.
 */
function getType() {
  var result = getValue(this, 'type');
  if (!result) {
    return this.isFunction() ? 'Function' : 'unknown';
  }
  return /^(?:array|function|object|regexp)$/.test(result)
    ? _.capitalize(result)
    : result;
}

/**
 * Checks if the entry is an alias.
 *
 * @memberOf Entry
 * @type {Function}
 * @returns {boolean} Returns `false`.
 */
var isAlias = _.constant(false);

/**
 * Checks if the entry is a constructor.
 *
 * @memberOf Entry
 * @returns {boolean} Returns `true` if a constructor, else `false`.
 */
function isCtor() {
  return hasTag(this, 'constructor');
}

/**
 * Checks if the entry is a function reference.
 *
 * @memberOf Entry
 * @returns {boolean} Returns `true` if the entry is a function reference, else `false`.
 */
function isFunction() {
  return !!(
    this.isCtor() ||
    _.size(this.getParams()) ||
    _.size(this.getReturns()) ||
    hasTag(this, 'function') ||
    /\*\/\s*function/.test(this.entry)
  );
}

/**
 * Checks if the entry is a license.
 *
 * @memberOf Entry
 * @returns {boolean} Returns `true` if a license, else `false`.
 */
function isLicense() {
  return hasTag(this, 'license');
}

/**
 * Checks if the entry *is* assigned to a prototype.
 *
 * @memberOf Entry
 * @returns {boolean} Returns `true` if assigned to a prototype, else `false`.
 */
function isPlugin() {
  return (
    !this.isCtor() &&
    !this.isPrivate() &&
    !this.isStatic()
  );
}

/**
 * Checks if the entry is private.
 *
 * @memberOf Entry
 * @returns {boolean} Returns `true` if private, else `false`.
 */
function isPrivate() {
  return (
    this.isLicense() ||
    hasTag(this, 'private') ||
    _.isEmpty(this.parsed.tags)
  );
}

/**
 * Checks if the entry is *not* assigned to a prototype.
 *
 * @memberOf Entry
 * @returns {boolean} Returns `true` if not assigned to a prototype, else `false`.
 */
function isStatic() {
  var isPublic = !this.isPrivate(),
      result = isPublic && hasTag(this, 'static');

  // Get the result in cases where it isn't explicitly stated.
  if (isPublic && !result) {
    var parent = _.last(_.toString(this.getMembers(0)).split(/[#.]/));
    if (!parent) {
      return true;
    }
    var source = this.source;
    _.each(getEntries(source), function(entry) {
      entry = new Entry(entry, source);
      if (entry.getName() == parent) {
        result = !entry.isCtor();
        return false;
      }
    });
  }
  return result;
}

/*----------------------------------------------------------------------------*/

Entry.getEntries = getEntries;

_.assign(Entry.prototype, {
  'getAliases': getAliases,
  'getCall': getCall,
  'getCategory': getCategory,
  'getDesc': getDesc,
  'getExample': getExample,
  'getHash': getHash,
  'getLineNumber': getLineNumber,
  'getMembers': getMembers,
  'getName': getName,
  'getParams': getParams,
  'getReturns': getReturns,
  'getSince': getSince,
  'getType': getType,
  'isAlias': isAlias,
  'isCtor': isCtor,
  'isFunction': isFunction,
  'isLicense': isLicense,
  'isPlugin': isPlugin,
  'isPrivate': isPrivate,
  'isStatic': isStatic
});

module.exports = Entry;
<commit_msg>Update `github` style of `getHash`.<commit_after>'use strict';

var _ = require('lodash'),
    fp = require('lodash/fp'),
    os = require('os'),
    Alias = require('./alias.js'),
    util = require('./util.js');

/*----------------------------------------------------------------------------*/

function getTag(entry, tagName) {
  var parsed = entry.parsed;
  return _.find(parsed.tags, ['title', tagName]);
}

function getValue(entry, tagName) {
  var parsed = entry.parsed,
      result = parsed.description,
      tag = getTag(entry, tagName);

  if (tagName == 'type') {
    result = _.get(tag, 'type.name');
  } else if (tagName != 'description') {
    result = _.get(tag, 'name') || _.get(tag, 'description');
  }
  return tagName == 'example'
    ? _.toString(result)
    : util.format(result);
}

function hasTag(entry, tagName) {
  return getTag(entry, tagName) !== undefined;
}

function reduceParamType(tag) {
  var expression = tag.expression,
      result = '',
      type = tag.type;

  if (type == 'AllLiteral') {
    result = '*';
  }
  if (type == 'NameExpression') {
    result = tag.name;
  }
  if (type == 'RestType') {
    result = '...' + result;
  }
  if (type == 'TypeApplication') {
    expression = undefined;
    result = _(tag)
      .chain()
      .get('applications')
      .map(_.flow(reduceParamType, fp.add(fp, '[]')))
      .sort(util.compareNatural)
      .join('|')
      .value();
  }
  if (type == 'UnionType') {
    result = _(tag)
      .chain()
      .get('elements')
      .map(reduceParamType)
      .sort(util.compareNatural)
      .join('|')
      .value();
  }
  if (expression) {
    result += reduceParamType(expression);
  }
  return type == 'UnionType'
    ? ('(' + result + ')')
    : result;
}

/*----------------------------------------------------------------------------*/

/**
 * The Entry constructor.
 *
 * @constructor
 * @param {string} entry The documentation entry to analyse.
 * @param {string} source The source code.
 * @param {string} [lang='js'] The language highlighter used for code examples.
 */
function Entry(entry, source, lang) {
  this.parsed = util.parse(
    entry
      .replace(/@param-/g, '@paramhidden')
      .replace(/(\*)\/\s*.+$/, '*')
  );

  this.entry = entry;
  this.lang = lang == null ? 'js' : lang;
  this.source = source.replace(os.EOL, '\n');
  this.getCall = _.memoize(this.getCall);
  this.getCategory = _.memoize(this.getCategory);
  this.getDesc = _.memoize(this.getDesc);
  this.getExample = _.memoize(this.getExample);
  this.getHash = _.memoize(this.getHash);
  this.getLineNumber = _.memoize(this.getLineNumber);
  this.getName = _.memoize(this.getName);
  this.getReturns = _.memoize(this.getReturns);
  this.getSince = _.memoize(this.getSince);
  this.getType = _.memoize(this.getType);
  this.isAlias = _.memoize(this.isAlias);
  this.isCtor = _.memoize(this.isCtor);
  this.isFunction = _.memoize(this.isFunction);
  this.isLicense = _.memoize(this.isLicense);
  this.isPlugin = _.memoize(this.isPlugin);
  this.isPrivate = _.memoize(this.isPrivate);
  this.isStatic = _.memoize(this.isStatic);
  this._aliases = this._members = this._params = undefined;
}

/**
 * Extracts the documentation entries from source code.
 *
 * @static
 * @memberOf Entry
 * @param {string} source The source code.
 * @returns {Array} The array of entries.
 */
function getEntries(source) {
  source = _.toString(source);
  return source.match(/\/\*\*(?![-!])[\s\S]*?\*\/\s*.+/g) || [];
}

/**
 * Extracts the entry's `alias` objects.
 *
 * @memberOf Entry
 * @param {number} index The index of the array value to return.
 * @returns {Array|string} The entry's `alias` objects.
 */
function getAliases(index) {
  if (this._aliases === undefined) {
    var owner = this;
    this._aliases = _(getValue(this, 'alias'))
      .split(/,\s*/)
      .compact()
      .sort(util.compareNatural)
      .map(function(value) { return new Alias(value, owner); })
      .value();
  }
  var result = this._aliases;
  return index === undefined ? result : result[index];
}

/**
 * Extracts the function call from the entry.
 *
 * @memberOf Entry
 * @returns {string} The function call.
 */
function getCall() {
  var result = /\*\/\s*(?:function\s+([^(]*)|(.*?)(?=[:=,]))/.exec(this.entry);
  if (result) {
    result = (result[1] || result[2]).split('.').pop();
    result = _.trim(_.trim(result), "'").split('var ').pop();
    result = _.trim(result);
  }
  var name = getValue(this, 'name') || result || '';
  if (!this.isFunction()) {
    return name;
  }
  var params = this.getParams();
  result = _.castArray(result);

  // Compile the function call syntax.
  _.each(params, function(param) {
    var paramValue = param[1],
        parentParam = _.result(/\w+(?=\.[\w.]+)/.exec(paramValue), 0);

    var parentIndex = parentParam == null ? -1 : _.findIndex(params, function(param) {
      return _.trim(param[1], '[]') == parentParam;
    });

    // Skip params that are properties of other params (e.g. `options.leading`).
    if (_.result(params[parentIndex], 0) != 'Object') {
      result.push(paramValue);
    }
  });

  // Format the function call.
  return name + '(' + result.slice(1).join(', ') + ')';
}

/**
 * Extracts the entry's `category` data.
 *
 * @memberOf Entry
 * @returns {string} The entry's `category` data.
 */
function getCategory() {
  var result = getValue(this, 'category');
  return result || (this.getType() == 'Function' ? 'Methods' : 'Properties');
}

/**
 * Extracts the entry's description.
 *
 * @memberOf Entry
 * @returns {string} The entry's description.
 */
function getDesc() {
  var type = this.getType(),
      result = getValue(this, 'description');

  return (!result || type == 'Function' || type == 'unknown')
    ? result
    : ('(' + _.trim(type.replace(/\|/g, ', '), '()') + '): ' + result);
}

/**
 * Extracts the entry's `example` data.
 *
 * @memberOf Entry
 * @returns {string} The entry's `example` data.
 */
function getExample() {
  var result = getValue(this, 'example');
  return result && ('```' + this.lang + '\n' + result + '\n```');
}

/**
 * Extracts the entry's hash value for permalinking.
 *
 * @memberOf Entry
 * @param {string} [style] The hash style.
 * @returns {string} The entry's hash value (without a hash itself).
 */
function getHash(style) {
  var result = this.getMembers(0) || '';

  switch (style) {
    case 'github':
      if (result) {
        result += this.isPlugin() ? 'prototype' : '';
      }
      result += this.getCall();
      return result
        .replace(/[\\.=|'"(){}\[\]\t ]/g, '')
        .replace(/[#,]+/g, '-')
        .toLowerCase();

    case 'default':
    default:
      if (result) {
        result += '-' + (this.isPlugin() ? 'prototype-' : '');
      }
      result += this.isAlias() ? this.getOwner().getName() : this.getName();
      return result
        .replace(/\./g, '-')
        .replace(/^_-/, '');
  }
}

/**
 * Resolves the entry's line number.
 *
 * @memberOf Entry
 * @returns {number} The entry's line number.
 */
function getLineNumber() {
  var lines = this.source
    .slice(0, this.source.indexOf(this.entry) + this.entry.length)
    .match(/\n/g)
    .slice(1);

  // Offset by 2 because the first line number is before a line break and the
  // last line doesn't include a line break.
  return lines.length + 2;
}

/**
 * Extracts the entry's `member` data.
 *
 * @memberOf Entry
 * @param {number} [index] The index of the array value to return.
 * @returns {Array|string} The entry's `member` data.
 */
function getMembers(index) {
  if (this._members === undefined) {
    this._members = _(getValue(this, 'member') || getValue(this, 'memberOf'))
      .split(/,\s*/)
      .compact()
      .sort(util.compareNatural)
      .value();
  }
  var result = this._members;
  return index === undefined ? result : result[index];
}

/**
 * Extracts the entry's `name` data.
 *
 * @memberOf Entry
 * @returns {string} The entry's `name` data.
 */
function getName() {
  return hasTag(this, 'name')
    ? getValue(this, 'name')
    : _.toString(_.first(this.getCall().split('(')));
}

/**
 * Extracts the entry's `param` data.
 *
 * @memberOf Entry
 * @param {number} [index] The index of the array value to return.
 * @returns {Array} The entry's `param` data.
 */
function getParams(index) {
  if (this._params === undefined) {
    this._params = _.map(_.filter(this.parsed.tags, ['title', 'param']), function(tag) {
      var defaultValue = tag['default'],
          desc = util.format(tag.description),
          name = tag.name || '',
          type = reduceParamType(tag.type);

      if (defaultValue != null) {
        name += '=' + defaultValue;
      }
      if (_.get(tag, 'type.type') == 'OptionalType') {
        name = '[' + name + ']';
      }
      return [type, name,  desc];
    });
  }
  var result = this._params;
  return index === undefined ? result : result[index];
}

/**
 * Extracts the entry's `returns` data.
 *
 * @memberOf Entry
 * @returns {array} The entry's `returns` data.
 */
function getReturns() {
  var tag = getTag(this, 'returns'),
      desc = _.get(tag, 'description') || '',
      type = _.get(tag, 'type.name') || '*';

  return tag ? [type, desc] : [];
}

/**
 * Extracts the entry's `since` data.
 *
 * @memberOf Entry
 * @returns {string} The entry's `since` data.
 */
function getSince() {
  return getValue(this, 'since');
}

/**
 * Extracts the entry's `type` data.
 *
 * @memberOf Entry
 * @returns {string} The entry's `type` data.
 */
function getType() {
  var result = getValue(this, 'type');
  if (!result) {
    return this.isFunction() ? 'Function' : 'unknown';
  }
  return /^(?:array|function|object|regexp)$/.test(result)
    ? _.capitalize(result)
    : result;
}

/**
 * Checks if the entry is an alias.
 *
 * @memberOf Entry
 * @type {Function}
 * @returns {boolean} Returns `false`.
 */
var isAlias = _.constant(false);

/**
 * Checks if the entry is a constructor.
 *
 * @memberOf Entry
 * @returns {boolean} Returns `true` if a constructor, else `false`.
 */
function isCtor() {
  return hasTag(this, 'constructor');
}

/**
 * Checks if the entry is a function reference.
 *
 * @memberOf Entry
 * @returns {boolean} Returns `true` if the entry is a function reference, else `false`.
 */
function isFunction() {
  return !!(
    this.isCtor() ||
    _.size(this.getParams()) ||
    _.size(this.getReturns()) ||
    hasTag(this, 'function') ||
    /\*\/\s*function/.test(this.entry)
  );
}

/**
 * Checks if the entry is a license.
 *
 * @memberOf Entry
 * @returns {boolean} Returns `true` if a license, else `false`.
 */
function isLicense() {
  return hasTag(this, 'license');
}

/**
 * Checks if the entry *is* assigned to a prototype.
 *
 * @memberOf Entry
 * @returns {boolean} Returns `true` if assigned to a prototype, else `false`.
 */
function isPlugin() {
  return (
    !this.isCtor() &&
    !this.isPrivate() &&
    !this.isStatic()
  );
}

/**
 * Checks if the entry is private.
 *
 * @memberOf Entry
 * @returns {boolean} Returns `true` if private, else `false`.
 */
function isPrivate() {
  return (
    this.isLicense() ||
    hasTag(this, 'private') ||
    _.isEmpty(this.parsed.tags)
  );
}

/**
 * Checks if the entry is *not* assigned to a prototype.
 *
 * @memberOf Entry
 * @returns {boolean} Returns `true` if not assigned to a prototype, else `false`.
 */
function isStatic() {
  var isPublic = !this.isPrivate(),
      result = isPublic && hasTag(this, 'static');

  // Get the result in cases where it isn't explicitly stated.
  if (isPublic && !result) {
    var parent = _.last(_.toString(this.getMembers(0)).split(/[#.]/));
    if (!parent) {
      return true;
    }
    var source = this.source;
    _.each(getEntries(source), function(entry) {
      entry = new Entry(entry, source);
      if (entry.getName() == parent) {
        result = !entry.isCtor();
        return false;
      }
    });
  }
  return result;
}

/*----------------------------------------------------------------------------*/

Entry.getEntries = getEntries;

_.assign(Entry.prototype, {
  'getAliases': getAliases,
  'getCall': getCall,
  'getCategory': getCategory,
  'getDesc': getDesc,
  'getExample': getExample,
  'getHash': getHash,
  'getLineNumber': getLineNumber,
  'getMembers': getMembers,
  'getName': getName,
  'getParams': getParams,
  'getReturns': getReturns,
  'getSince': getSince,
  'getType': getType,
  'isAlias': isAlias,
  'isCtor': isCtor,
  'isFunction': isFunction,
  'isLicense': isLicense,
  'isPlugin': isPlugin,
  'isPrivate': isPrivate,
  'isStatic': isStatic
});

module.exports = Entry;
<|endoftext|>


<commit_before>var _ = require('lodash');
var util = require('util');

function GitHubError(opts) {
    opts = _.defaults(opts || {}, {
        statusCode: 0,
        body: {}
    });

    if (_.isString(body)) {
        try {
            opts.body = JSON.parse(opts.body);
        } catch (e) {
            // ignore json error
        }
    }

    this.name = 'GitHubError';
    this.message = "Error "+opts.statusCode;
    this.statusCode = opts.statusCode
    this.result = opts;
    this.stack = (new Error()).stack;

    var body = opts.body;
    if (body && body.message) {
        this.message = body.message;
        this.documentationUrl = body.documentation_url;
        this.errors = body.errors || [];
    }
}
util.inherits(GitHubError, Error);

module.exports = GitHubError;
<commit_msg>improve capture stack in gherror<commit_after>var _ = require('lodash');
var util = require('util');

function GitHubError(opts) {
    Error.captureStackTrace(this, this.constructor);

    opts = _.defaults(opts || {}, {
        statusCode: 0,
        body: {}
    });

    this.name = 'GitHubError';
    this.message = "Error "+opts.statusCode;
    this.statusCode = opts.statusCode
    this.result = opts;

    var body = opts.body;
    if (body && body.message) {
        this.message = body.message;
        this.documentationUrl = body.documentation_url;
        this.errors = body.errors || [];
    }
}
util.inherits(GitHubError, Error);

module.exports = GitHubError;
<|endoftext|>


<commit_before>/*
 * The MIT License
 *
 * Copyright 2014 Timo Behrmann, Guillaume Chauvet.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
var assert = require('assert');

module.exports.handle = function (errors, req, res, options, next) {
    if(options.errorHandler) {
        if(typeof(options.errorHandler) === 'function') {
            return options.errorHandler(res, errors);
        } else {
            return res.send(new options.errorHandler(errors));
        }
    } else {
        return res.send (400, {
            status: 'validation failed',
            errors: errors
        });
    }
};
<commit_msg>Probably solves this issue https://github.com/z0mt3c/node-restify-validation/issues/26<commit_after>/*
 * The MIT License
 *
 * Copyright 2014 Timo Behrmann, Guillaume Chauvet.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
var assert = require('assert');

module.exports.handle = function (errors, req, res, options, next) {
    if(options.errorHandler && typeof(options.errorHandler) === 'function') {
        return res.send(new options.errorHandler(errors));
    } else {
        return res.send (400, {
            status: 'validation failed',
            errors: errors
        });
    }
};
<|endoftext|>


<commit_before>const async = require('async');
const fs = require('fs');
const fse = require('fs-extra');
const https = require('https');
const path = require('path');

module.exports = {

  // get the sfdx-oss-manifest.json file
  getManifest: (rawUrlManifest, result) => {

    https.get(rawUrlManifest, (manifestResponse) => {
      let body = '';

      manifestResponse.on('data', (chunk) => {
        body += chunk;
      });

      manifestResponse.on('end', () => {
        result(body);
      });

    }).on('error', (err) => {
      console.log(`Error when trying to retrieve sfdx-oss-manifest.json file: ${err}`); // eslint-disable-line no-console
    });
  },

  // download the files and write the files to the filesystem
  writeFiles: (manifestFiles, targetPath, rawUrlManifestFolder, done) => {

    let key = 0;

    async.each(manifestFiles, (fileName, callback) => {

      if (key === 0) {
        // console.log('Writing files ...'); // eslint-disable-line no-console
      }

      const filePathAndName = path.join(targetPath, fileName);
      const filePath = path.dirname(filePathAndName);

      fse.ensureDirSync(filePath);

      const localFile = fs.createWriteStream(filePathAndName);
      const fileUrl = `${rawUrlManifestFolder}/${fileName}`;

      https.get(fileUrl, (fileResponse) => {
        fileResponse.pipe(localFile);
        // console.log(`  ${filePathAndName}`); // eslint-disable-line no-console
      });

      key++;

      callback();

    });

    done(manifestFiles);
  }
};<commit_msg>Refactoring and removing unneeded items<commit_after>const async = require('async');
const fs = require('fs');
const fse = require('fs-extra');
const https = require('https');
const path = require('path');

module.exports = {

  // get the sfdx-oss-manifest.json file
  getManifest: (rawUrlManifest, result) => {

    https.get(rawUrlManifest, (manifestResponse) => {
      let body = '';

      manifestResponse.on('data', (chunk) => {
        body += chunk;
      });

      manifestResponse.on('end', () => {
        result(body);
      });

    }).on('error', (err) => {
      console.log(`Error when trying to retrieve sfdx-oss-manifest.json file: ${err}`); // eslint-disable-line no-console
    });
  },

  // download the files and write the files to the filesystem
  writeFiles: (manifestFiles, targetPath, rawUrlManifestFolder, done) => {

    async.each(manifestFiles, (fileName, callback) => {

      const filePathAndName = path.join(targetPath, fileName);
      const filePath = path.dirname(filePathAndName);

      fse.ensureDirSync(filePath);

      const localFile = fs.createWriteStream(filePathAndName);
      const fileUrl = `${rawUrlManifestFolder}/${fileName}`;

      https.get(fileUrl, (fileResponse) => {
        fileResponse.pipe(localFile);
      });

      callback();

    });

    done(manifestFiles);
  }
};<|endoftext|>