<commit_before>import config from 'config';
import _, { isArray, pick, get } from 'lodash';
import Promise from 'bluebird';
import juice from 'juice';
import nodemailer from 'nodemailer';
import debugLib from 'debug';
import templates from './emailTemplates';
import { isEmailInternal } from './utils';
import crypto from 'crypto';
import fs from 'fs';
import path from 'path';
import he from 'he';
const debug = debugLib('email');

const render = (template, data) => {
  let text;
  data.imageNotSvg =
    data.collective &&
    data.collective.image &&
    !data.collective.image.endsWith('.svg');
  data = _.merge({}, data);
  delete data.config;
  data.config = { host: config.host };

  // sets paypalEmail for purpose of email templates
  if (data.user) {
    data.user.paypalEmail = data.user.paypalEmail || data.user.email;
  }

  if (templates[`${template}.text`]) {
    text = templates[`${template}.text`](data);
  }
  const html = juice(he.decode(templates[template](data)));

  // When in development mode, we log the data used to compile the template
  // (useful to get login token without sending an email)
  debugLib('data')(`Rendering ${template} with data`, data);

  return { text, html };
};

const generateUnsubscribeToken = (email, collectiveSlug, type) => {
  const uid = `${email}.${collectiveSlug || 'any'}.${type}.${
    config.keys.opencollective.secret
  }`;
  const token = crypto
    .createHash('md5')
    .update(uid)
    .digest('hex');
  return token;
};

/*
 * Gets the body from a string (usually a template)
 */
const getTemplateAttributes = str => {
  let index = 0;
  const lines = str.split('\n');
  const attributes = {};
  let tokens;
  do {
    tokens = lines[index++].match(/^([a-z]+):(.+)/i);
    if (tokens) {
      attributes[tokens[1].toLowerCase()] = tokens[2]
        .replace(/<br( \/)?>/g, '\n')
        .trim();
    }
  } while (tokens);

  attributes.body = lines
    .slice(index)
    .join('\n')
    .trim();
  return attributes;
};

/*
 * sends an email message to a recipient with given subject and body
 */
const sendMessage = (recipients, subject, html, options = {}) => {
  options.bcc = options.bcc || 'emailbcc@opencollective.com';

  if (!isArray(recipients)) recipients = [recipients];

  recipients = recipients.filter(recipient => {
    if (!recipient || !recipient.match(/.+@.+\..+/)) {
      debug(`${recipient} is an invalid email address, skipping`);
      return false;
    }
    // if not in production, only send out emails to bcc'd opencollective address
    if (process.env.NODE_ENV !== 'production' && !isEmailInternal(recipient)) {
      debug(
        `${recipient} is an external email address, skipping in development environment`,
      );
      return false;
    } else {
      return true;
    }
  });

  if (process.env.NODE_ENV === 'staging') {
    subject = `[STAGING] ${subject}`;
  } else if (
    process.env.NODE_ENV !== 'production' &&
    process.env.WEBSITE_URL !== 'https://opencollective.com'
  ) {
    subject = `[TESTING] ${subject}`;
  }

  let to;
  if (recipients.length > 0) {
    to = recipients.join(', ');
  }

  // When in preview mode, we export an HTML version of the email in `/tmp/:template.:recipientSlug.html`
  if (process.env.DEBUG && process.env.DEBUG.match(/preview/)) {
    let filepath;
    const recipientSlug = to.substr(0, to.indexOf('@'));
    filepath = path.resolve(`/tmp/${options.tag}.${recipientSlug}.html`);
    fs.writeFileSync(filepath, html);
    console.log('>>> preview email', filepath);
    if (options.text) {
      filepath = `/tmp/${options.tag}.${recipientSlug}.txt`;
      fs.writeFileSync(filepath, options.text);
      console.log('>>> preview email', filepath);
    }

    if (options.attachments) {
      options.attachments.map(attachment => {
        const filepath = path.resolve(`/tmp/${attachment.filename}`);
        fs.writeFileSync(filepath, attachment.content);
        console.log('>>> preview attachment', filepath);
      });
    }
  }

  if (process.env.ONLY) {
    debug('Only sending email to ', process.env.ONLY);
    to = process.env.ONLY;
  } else if (process.env.NODE_ENV !== 'production') {
    if (!to) {
      return Promise.reject(
        new Error('emailLib.sendMessage error: No recipient defined'),
      );
    }
    to = `emailbcc+${to.replace(/@/g, '-at-')}@opencollective.com`;
  }

  debug(`sending email to ${to}`);
  if (recipients.length === 0) {
    debug(
      'emailLib.sendMessage error: No recipient to send to, only sending to bcc',
      options.bcc,
    );
  }

  if (config.mailgun.user) {
    const mailgun = nodemailer.createTransport({
      service: 'Mailgun',
      auth: {
        user: config.mailgun.user,
        pass: config.mailgun.password,
      },
    });

    return new Promise((resolve, reject) => {
      const from = options.from || config.email.from;
      const cc = options.cc;
      const bcc = options.bcc;
      const text = options.text;
      const attachments = options.attachments;

      // only attach tag in production to keep data clean
      const tag =
        process.env.NODE_ENV === 'production' ? options.tag : 'internal';
      const headers = { 'X-Mailgun-Tag': tag, 'X-Mailgun-Dkim': 'yes' };
      debug('mailgun> sending email to ', to, 'bcc', bcc);

      return mailgun.sendMail(
        { from, cc, to, bcc, subject, text, html, headers, attachments },
        (err, info) => {
          if (err) {
            debug('>>> mailgun.sendMail error', err);
            return reject(err);
          } else {
            debug('>>> mailgun.sendMail success', info);
            return resolve(info);
          }
        },
      );
    });
  } else {
    debug('>>> mailgun not configured');
    debugLib('text')(options.text);
    debugLib('html')(html);
    return Promise.resolve();
  }
};

/**
 * Get the label to unsubscribe from the email notification
 * Shown in the footer of the email following "To unsubscribe from "
 */
const getNotificationLabel = (template, recipients) => {
  if (!isArray(recipients)) recipients = [recipients];

  template = template.replace('.text', '');

  const notificationTypeLabels = {
    'email.approve': 'notifications of new emails pending approval',
    'email.message': `the ${recipients[0].substr(
      0,
      recipients[0].indexOf('@'),
    )} mailing list`,
    'collective.order.created':
      'notifications of new donations for this collective',
    'collective.comment.created':
      'notifications of new comments submitted to this collective',
    'collective.expense.created':
      'notifications of new expenses submitted to this collective',
    'collective.expense.approved.for.host':
      'notifications of new expenses approved under this host',
    'collective.expense.paid.for.host':
      'notifications of new expenses paid under this host',
    'collective.monthlyreport': 'monthly reports for collectives',
    'collective.member.created': 'notifications of new members',
    'collective.update.published':
      'notifications of new updates from this collective',
    'host.monthlyreport': 'monthly reports for host',
    'host.yearlyreport': 'yearly reports for host',
    'collective.transaction.created':
      'notifications of new transactions for this collective',
    onboarding: 'onboarding emails',
    'user.monthlyreport': 'monthly reports for backers',
    'user.yearlyreport': 'yearly reports',
  };

  return notificationTypeLabels[template];
};

/*
 * Given a template, recipient and data, generates email.
 */
const generateEmailFromTemplate = (
  template,
  recipient,
  data = {},
  options = {},
) => {
  const slug =
    get(options, 'collective.slug') ||
    get(data, 'collective.slug') ||
    'undefined';

  // If we are sending the same email to multiple recipients, it doesn't make sense to allow them to unsubscribe
  if (!isArray(recipient)) {
    data.notificationTypeLabel = getNotificationLabel(
      options.type || template,
      recipient,
    );
    data.unsubscribeUrl = `${
      config.host.website
    }/api/services/email/unsubscribe/${encodeURIComponent(
      recipient || options.bcc,
    )}/${slug}/${options.type || template}/${generateUnsubscribeToken(
      recipient || options.bcc,
      slug,
      options.type || template,
    )}`;
  }

  if (template === 'ticket.confirmed') {
    if (slug === 'sustainoss') template += '.sustainoss';
    if (slug === 'fearlesscitiesbrussels')
      template += '.fearlesscitiesbrussels';
  }
  if (template.match(/^host\.(monthly|yearly)report$/)) {
    template = 'host.report';
  }
  if (template === 'donationmatched') {
    if (slug.match(/wwcode/)) template += '.wwcode';
  }
  if (template === 'thankyou') {
    if (slug.match(/wwcode/)) template += '.wwcode';

    if (
      _.contains(
        ['chsf', 'kendraio', 'brusselstogether', 'sustainoss', 'ispcwa'],
        slug,
      )
    ) {
      template = `thankyou.${slug}`;
    }

    if (
      _.contains(
        [
          'laprimaire',
          'lesbarbares',
          'nuitdebout',
          'enmarchebe',
          'monnaie-libre',
        ],
        slug,
      )
    ) {
      template += '.fr';

      if (slug === 'laprimaire') {
        template = 'thankyou.laprimaire';
      }

      // xdamman: hack
      switch (data.interval) {
        case 'month':
          data.interval = 'mois';
          break;
        case 'year':
          data.interval = 'an';
          break;
      }
    }
  }

  if (template === 'collective.member.created') {
    if (
      get(data, 'member.memberCollective.twitterHandle') &&
      get(data, 'member.role') === 'BACKER'
    ) {
      const collectiveMention = get(data, 'collective.twitterHandle')
        ? `@${data.collective.twitterHandle}`
        : data.collective.name;
      const text = `Hi @${
        data.member.memberCollective.twitterHandle
      } thanks for your donation to ${collectiveMention} ${
        config.host.website
      }${get(data, 'collective.urlPath')} 🎉😊`;
      data.tweet = {
        text,
        encoded: encodeURIComponent(text),
      };
    }
  }

  data.config = pick(config, ['host']);
  data.utm = `utm_source=opencollective&utm_campaign=${template}&utm_medium=email`;

  if (!templates[template]) {
    return Promise.reject(new Error(`Invalid email template: ${template}`));
  }

  const renderedTemplate = render(template, data);

  return Promise.resolve(renderedTemplate);
};

/*
 * Given a template, recipient and data, generates email and sends it.
 */
const generateEmailFromTemplateAndSend = (
  template,
  recipient,
  data,
  options = {},
) => {
  if (!recipient) {
    return Promise.reject('No recipient');
  }
  return generateEmailFromTemplate(template, recipient, data, options).then(
    renderedTemplate => {
      const attributes = getTemplateAttributes(renderedTemplate.html);
      options.text = renderedTemplate.text;
      options.tag = template;
      return emailLib.sendMessage(
        recipient,
        attributes.subject,
        attributes.body,
        options,
      );
    },
  );
};

const emailLib = {
  render,
  getTemplateAttributes,
  sendMessage,
  generateEmailFromTemplate,
  send: generateEmailFromTemplateAndSend,
};

export default emailLib;
<commit_msg>feat(lib/email): support local maildev<commit_after>import config from 'config';
import _, { isArray, pick, get } from 'lodash';
import Promise from 'bluebird';
import juice from 'juice';
import nodemailer from 'nodemailer';
import debugLib from 'debug';
import templates from './emailTemplates';
import { isEmailInternal } from './utils';
import crypto from 'crypto';
import fs from 'fs';
import path from 'path';
import he from 'he';
const debug = debugLib('email');

const render = (template, data) => {
  let text;
  data.imageNotSvg =
    data.collective &&
    data.collective.image &&
    !data.collective.image.endsWith('.svg');
  data = _.merge({}, data);
  delete data.config;
  data.config = { host: config.host };

  // sets paypalEmail for purpose of email templates
  if (data.user) {
    data.user.paypalEmail = data.user.paypalEmail || data.user.email;
  }

  if (templates[`${template}.text`]) {
    text = templates[`${template}.text`](data);
  }
  const html = juice(he.decode(templates[template](data)));

  // When in development mode, we log the data used to compile the template
  // (useful to get login token without sending an email)
  debugLib('data')(`Rendering ${template} with data`, data);

  return { text, html };
};

const generateUnsubscribeToken = (email, collectiveSlug, type) => {
  const uid = `${email}.${collectiveSlug || 'any'}.${type}.${
    config.keys.opencollective.secret
  }`;
  const token = crypto
    .createHash('md5')
    .update(uid)
    .digest('hex');
  return token;
};

/*
 * Gets the body from a string (usually a template)
 */
const getTemplateAttributes = str => {
  let index = 0;
  const lines = str.split('\n');
  const attributes = {};
  let tokens;
  do {
    tokens = lines[index++].match(/^([a-z]+):(.+)/i);
    if (tokens) {
      attributes[tokens[1].toLowerCase()] = tokens[2]
        .replace(/<br( \/)?>/g, '\n')
        .trim();
    }
  } while (tokens);

  attributes.body = lines
    .slice(index)
    .join('\n')
    .trim();
  return attributes;
};

/*
 * sends an email message to a recipient with given subject and body
 */
const sendMessage = (recipients, subject, html, options = {}) => {
  options.bcc = options.bcc || 'emailbcc@opencollective.com';

  if (!isArray(recipients)) recipients = [recipients];

  recipients = recipients.filter(recipient => {
    if (!recipient || !recipient.match(/.+@.+\..+/)) {
      debug(`${recipient} is an invalid email address, skipping`);
      return false;
    }
    // if not in production, only send out emails to bcc'd opencollective address
    if (process.env.NODE_ENV !== 'production' && !isEmailInternal(recipient)) {
      debug(
        `${recipient} is an external email address, skipping in development environment`,
      );
      return false;
    } else {
      return true;
    }
  });

  if (process.env.NODE_ENV === 'staging') {
    subject = `[STAGING] ${subject}`;
  } else if (
    process.env.NODE_ENV !== 'production' &&
    process.env.WEBSITE_URL !== 'https://opencollective.com'
  ) {
    subject = `[TESTING] ${subject}`;
  }

  let to;
  if (recipients.length > 0) {
    to = recipients.join(', ');
  }

  // When in preview mode, we export an HTML version of the email in `/tmp/:template.:recipientSlug.html`
  if (process.env.DEBUG && process.env.DEBUG.match(/preview/)) {
    let filepath;
    const recipientSlug = to.substr(0, to.indexOf('@'));
    filepath = path.resolve(`/tmp/${options.tag}.${recipientSlug}.html`);
    fs.writeFileSync(filepath, html);
    console.log('>>> preview email', filepath);
    if (options.text) {
      filepath = `/tmp/${options.tag}.${recipientSlug}.txt`;
      fs.writeFileSync(filepath, options.text);
      console.log('>>> preview email', filepath);
    }

    if (options.attachments) {
      options.attachments.map(attachment => {
        const filepath = path.resolve(`/tmp/${attachment.filename}`);
        fs.writeFileSync(filepath, attachment.content);
        console.log('>>> preview attachment', filepath);
      });
    }
  }

  if (process.env.ONLY) {
    debug('Only sending email to ', process.env.ONLY);
    to = process.env.ONLY;
  } else if (process.env.NODE_ENV !== 'production') {
    if (!to) {
      return Promise.reject(
        new Error('emailLib.sendMessage error: No recipient defined'),
      );
    }
    to = `emailbcc+${to.replace(/@/g, '-at-')}@opencollective.com`;
  }

  debug(`sending email to ${to}`);
  if (recipients.length === 0) {
    debug(
      'emailLib.sendMessage error: No recipient to send to, only sending to bcc',
      options.bcc,
    );
  }

  if (config.mailgun.user || process.env.MAILDEV) {
    const transport = process.env.MAILDEV ? {
      ignoreTLS: true,
      port: 1025,
    } : {
      service: 'Mailgun',
      auth: {
        user: config.mailgun.user,
        pass: config.mailgun.password,
      },
    };
    const mailgun = nodemailer.createTransport(transport);

    return new Promise((resolve, reject) => {
      const from = options.from || config.email.from;
      const cc = options.cc;
      const bcc = options.bcc;
      const text = options.text;
      const attachments = options.attachments;

      // only attach tag in production to keep data clean
      const tag =
        process.env.NODE_ENV === 'production' ? options.tag : 'internal';
      const headers = { 'X-Mailgun-Tag': tag, 'X-Mailgun-Dkim': 'yes' };
      debug('mailgun> sending email to ', to, 'bcc', bcc);

      return mailgun.sendMail(
        { from, cc, to, bcc, subject, text, html, headers, attachments },
        (err, info) => {
          if (err) {
            debug('>>> mailgun.sendMail error', err);
            return reject(err);
          } else {
            debug('>>> mailgun.sendMail success', info);
            return resolve(info);
          }
        },
      );
    });
  } else {
    debug('>>> mailgun not configured');
    debugLib('text')(options.text);
    debugLib('html')(html);
    return Promise.resolve();
  }
};

/**
 * Get the label to unsubscribe from the email notification
 * Shown in the footer of the email following "To unsubscribe from "
 */
const getNotificationLabel = (template, recipients) => {
  if (!isArray(recipients)) recipients = [recipients];

  template = template.replace('.text', '');

  const notificationTypeLabels = {
    'email.approve': 'notifications of new emails pending approval',
    'email.message': `the ${recipients[0].substr(
      0,
      recipients[0].indexOf('@'),
    )} mailing list`,
    'collective.order.created':
      'notifications of new donations for this collective',
    'collective.comment.created':
      'notifications of new comments submitted to this collective',
    'collective.expense.created':
      'notifications of new expenses submitted to this collective',
    'collective.expense.approved.for.host':
      'notifications of new expenses approved under this host',
    'collective.expense.paid.for.host':
      'notifications of new expenses paid under this host',
    'collective.monthlyreport': 'monthly reports for collectives',
    'collective.member.created': 'notifications of new members',
    'collective.update.published':
      'notifications of new updates from this collective',
    'host.monthlyreport': 'monthly reports for host',
    'host.yearlyreport': 'yearly reports for host',
    'collective.transaction.created':
      'notifications of new transactions for this collective',
    onboarding: 'onboarding emails',
    'user.monthlyreport': 'monthly reports for backers',
    'user.yearlyreport': 'yearly reports',
  };

  return notificationTypeLabels[template];
};

/*
 * Given a template, recipient and data, generates email.
 */
const generateEmailFromTemplate = (
  template,
  recipient,
  data = {},
  options = {},
) => {
  const slug =
    get(options, 'collective.slug') ||
    get(data, 'collective.slug') ||
    'undefined';

  // If we are sending the same email to multiple recipients, it doesn't make sense to allow them to unsubscribe
  if (!isArray(recipient)) {
    data.notificationTypeLabel = getNotificationLabel(
      options.type || template,
      recipient,
    );
    data.unsubscribeUrl = `${
      config.host.website
    }/api/services/email/unsubscribe/${encodeURIComponent(
      recipient || options.bcc,
    )}/${slug}/${options.type || template}/${generateUnsubscribeToken(
      recipient || options.bcc,
      slug,
      options.type || template,
    )}`;
  }

  if (template === 'ticket.confirmed') {
    if (slug === 'sustainoss') template += '.sustainoss';
    if (slug === 'fearlesscitiesbrussels')
      template += '.fearlesscitiesbrussels';
  }
  if (template.match(/^host\.(monthly|yearly)report$/)) {
    template = 'host.report';
  }
  if (template === 'donationmatched') {
    if (slug.match(/wwcode/)) template += '.wwcode';
  }
  if (template === 'thankyou') {
    if (slug.match(/wwcode/)) template += '.wwcode';

    if (
      _.contains(
        ['chsf', 'kendraio', 'brusselstogether', 'sustainoss', 'ispcwa'],
        slug,
      )
    ) {
      template = `thankyou.${slug}`;
    }

    if (
      _.contains(
        [
          'laprimaire',
          'lesbarbares',
          'nuitdebout',
          'enmarchebe',
          'monnaie-libre',
        ],
        slug,
      )
    ) {
      template += '.fr';

      if (slug === 'laprimaire') {
        template = 'thankyou.laprimaire';
      }

      // xdamman: hack
      switch (data.interval) {
        case 'month':
          data.interval = 'mois';
          break;
        case 'year':
          data.interval = 'an';
          break;
      }
    }
  }

  if (template === 'collective.member.created') {
    if (
      get(data, 'member.memberCollective.twitterHandle') &&
      get(data, 'member.role') === 'BACKER'
    ) {
      const collectiveMention = get(data, 'collective.twitterHandle')
        ? `@${data.collective.twitterHandle}`
        : data.collective.name;
      const text = `Hi @${
        data.member.memberCollective.twitterHandle
      } thanks for your donation to ${collectiveMention} ${
        config.host.website
      }${get(data, 'collective.urlPath')} 🎉😊`;
      data.tweet = {
        text,
        encoded: encodeURIComponent(text),
      };
    }
  }

  data.config = pick(config, ['host']);
  data.utm = `utm_source=opencollective&utm_campaign=${template}&utm_medium=email`;

  if (!templates[template]) {
    return Promise.reject(new Error(`Invalid email template: ${template}`));
  }

  const renderedTemplate = render(template, data);

  return Promise.resolve(renderedTemplate);
};

/*
 * Given a template, recipient and data, generates email and sends it.
 */
const generateEmailFromTemplateAndSend = (
  template,
  recipient,
  data,
  options = {},
) => {
  if (!recipient) {
    return Promise.reject('No recipient');
  }
  return generateEmailFromTemplate(template, recipient, data, options).then(
    renderedTemplate => {
      const attributes = getTemplateAttributes(renderedTemplate.html);
      options.text = renderedTemplate.text;
      options.tag = template;
      return emailLib.sendMessage(
        recipient,
        attributes.subject,
        attributes.body,
        options,
      );
    },
  );
};

const emailLib = {
  render,
  getTemplateAttributes,
  sendMessage,
  generateEmailFromTemplate,
  send: generateEmailFromTemplateAndSend,
};

export default emailLib;
<|endoftext|>


<commit_before>
"use strict";

const
  moment = require('moment'),
  Promise= require('bluebird'),
  Joi    = require('joi');

const

  schema_user = Joi
    .object()
    .required(),
//    .options({ allowUnknown : true })
//    .keys({
//      promise_total_number_of_days_in_allowance : Joi.func().required(),
//    }),

  schema_year = Joi
    .object()
    .type(moment)
    .default(() => moment.utc(), 'Default year is current one'),

  schema_promise_allowance = Joi.object().required().keys({
    year : schema_year,
    user : schema_user,
  }),

  scheme_constructor = Joi.object().required().keys({
    year : schema_year,
    user : schema_user,
    total_number_of_days_in_allowance     : Joi.number().required(),
    number_of_days_available_in_allowance : Joi.number().required(),
  });

class UserAllowance {

  constructor(args) {

    args = Joi.attempt(
      args,
      scheme_constructor,
      'Failed parameters validation for UserAllowance constructor'
    );

    let self = this;

    self._user = args.user;

    self._total_number_of_days_in_allowance = args.total_number_of_days_in_allowance;
    Object.defineProperty(self, 'total_number_of_days_in_allowance', {
      get : () => self._total_number_of_days_in_allowance,
    });

    self._number_of_days_available_in_allowance = args.number_of_days_available_in_allowance;
    Object.defineProperty(self, 'number_of_days_available_in_allowance', {
      get : () => self._number_of_days_available_in_allowance,
    });
  }

  /*
   *  The idea of this static method is to be constructor for UserAllowance class.
   *  It takes parameters and perform all necessary (and costly) actions to fetch
   *  all info required for allowance calculation for given user and year.
   * */
  static promise_allowance(args) {

    args = Joi.attempt(
      args,
      schema_promise_allowance,
      'Failed to validate parameters for promise_allowance'
    );

    let
      user = args.user,
      year = args.year,
      number_of_days_available_in_allowance,
      total_number_of_days_in_allowance;

    return Promise

      // We code below relies on user to have my_leaves fetched onto it, so do it if needed
      .resolve(
        user.my_leaves === undefined
        ? user.reload_with_leave_details({ year : year })
        : 1
      )

      // Fetch total number of days in employees allowance
      .then(() => promise_total_number_of_days_in_allowance.call(user, year))
      .then(num => Promise.resolve( total_number_of_days_in_allowance = num))

      .then(() => promise_number_of_days_available_in_allowance.call(user, year.format('YYYY')))
      .then(num => Promise.resolve( number_of_days_available_in_allowance = num ))

      // Got all necessary data for UserAllowance object so build it!
      .then(() => {

        return new UserAllowance({
          user : user,
          year : year,

          total_number_of_days_in_allowance     : total_number_of_days_in_allowance,
          number_of_days_available_in_allowance : number_of_days_available_in_allowance,
        });
      })

  }

}

/*
 * THis function used to be a method on user object.
 *
 *
 *
 * Calculate number of days available in allowance for current employee.
 *
 * */
function promise_number_of_days_available_in_allowance(year) {
  let self = this;

  if ( ! year ){
    year = moment.utc().format('YYYY');
  }

  // To avoid redundant trips to DB check cached value first
  if ( self.tom_cach &&
    self.tom_cach.hasOwnProperty('number_of_days_available_in_allowance') &&
    self.tom_cach.number_of_days_available_in_allowance.hasOwnProperty( year )
  ) {
    return Promise.resolve(
      self.tom_cach.number_of_days_available_in_allowance[year]
    );
  }

  return promise_total_number_of_days_in_allowance.call(self, year)

    .then(total_days_number => total_days_number - self.calculate_number_of_days_taken_from_allowance({year : year}))

    .then(number_of_days => {

      // Cach calculated value
      if ( ! self.hasOwnProperty('tom_cach') ) {
        self.tom_cach = {};
      }

      if ( ! self.tom_cach.hasOwnProperty('number_of_days_available_in_allowance')) {
        self.tom_cach.number_of_days_available_in_allowance = {};
      }

      self.tom_cach.number_of_days_available_in_allowance[year] = number_of_days;

      return Promise.resolve( number_of_days );
    });
}

function promise_total_number_of_days_in_allowance(year){
  let self = this;

  return Promise

    // First ensure that all necessary properties are available
    .try(() => {
      if ( ! self.department) {
        return self.getDepartment()
          .then(department => {
            self.department = department;
            return Promise.resolve();
          })
      }

      return Promise.resolve();
    })

    // Fetch Adjustment and Carried over allowance for current year
    .then(() => self.promise_adjustment_and_carry_over_for_year(year))

    .then(adjustment_and_coa => Promise.resolve(
      self._calculate_total_number_of_days_n_allowance({
        year       : year,
        adjustment : adjustment_and_coa.adjustment,
        carried_over_allowance : adjustment_and_coa.carried_over_allowance,
      })
    ));
};

module.exports = UserAllowance;
<commit_msg>Remove redundant code<commit_after>
"use strict";

const
  moment = require('moment'),
  Promise= require('bluebird'),
  Joi    = require('joi');

const

  schema_user = Joi
    .object()
    .required(),
//    .options({ allowUnknown : true })
//    .keys({
//      promise_total_number_of_days_in_allowance : Joi.func().required(),
//    }),

  schema_year = Joi
    .object()
    .type(moment)
    .default(() => moment.utc(), 'Default year is current one'),

  schema_promise_allowance = Joi.object().required().keys({
    year : schema_year,
    user : schema_user,
  }),

  scheme_constructor = Joi.object().required().keys({
    year : schema_year,
    user : schema_user,
    total_number_of_days_in_allowance     : Joi.number().required(),
    number_of_days_taken_from_allowance   : Joi.number().required(),
  });

class UserAllowance {

  constructor(args) {

    args = Joi.attempt(
      args,
      scheme_constructor,
      'Failed parameters validation for UserAllowance constructor'
    );

    let self = this;

    self._user = args.user;
    self._total_number_of_days_in_allowance     = args.total_number_of_days_in_allowance;
    self._number_of_days_taken_from_allowance   = args.number_of_days_taken_from_allowance;

    Object.defineProperty(self, 'total_number_of_days_in_allowance', {
      get : () => self._total_number_of_days_in_allowance,
    });

    Object.defineProperty(self, 'number_of_days_taken_from_allowance', {
      get : () => self._number_of_days_taken_from_allowance,
    });

    Object.defineProperty(self, 'number_of_days_available_in_allowance', {
      get : () => self.total_number_of_days_in_allowance - self.number_of_days_taken_from_allowance,
    });

  }

  /*
   *  The idea of this static method is to be constructor for UserAllowance class.
   *  It takes parameters and perform all necessary (and costly) actions to fetch
   *  all info required for allowance calculation for given user and year.
   * */
  static promise_allowance(args) {

    args = Joi.attempt(
      args,
      schema_promise_allowance,
      'Failed to validate parameters for promise_allowance'
    );

    let
      user = args.user,
      year = args.year,
      number_of_days_taken_from_allowance,
      total_number_of_days_in_allowance;

    return Promise

      // We code below relies on user to have my_leaves fetched onto it, so do it if needed
      .resolve(
        user.my_leaves === undefined
        ? user.reload_with_leave_details({ year : year })
        : 1
      )

      // Fetch total number of days in employees allowance
      .then(() => promise_total_number_of_days_in_allowance.call(user, year))
      .then(num => Promise.resolve( total_number_of_days_in_allowance = num))

      .then(() => Promise.resolve( number_of_days_taken_from_allowance = user.calculate_number_of_days_taken_from_allowance({ year : year.format('YYYY') }) ))

      // Got all necessary data for UserAllowance object so build it!
      .then(() => {

        return new UserAllowance({
          user : user,
          year : year,

          total_number_of_days_in_allowance     : total_number_of_days_in_allowance,
          number_of_days_taken_from_allowance   : number_of_days_taken_from_allowance,
        });
      });

  }

}

/*
 * THis function used to be a method on user object.
 *
 * */

function promise_total_number_of_days_in_allowance(year){
  let self = this;

  return Promise

    // First ensure that all necessary properties are available
    .try(() => {
      if ( ! self.department) {
        return self.getDepartment()
          .then(department => {
            self.department = department;
            return Promise.resolve();
          })
      }

      return Promise.resolve();
    })

    // Fetch Adjustment and Carried over allowance for current year
    .then(() => self.promise_adjustment_and_carry_over_for_year(year))

    .then(adjustment_and_coa => Promise.resolve(
      self._calculate_total_number_of_days_n_allowance({
        year       : year,
        adjustment : adjustment_and_coa.adjustment,
        carried_over_allowance : adjustment_and_coa.carried_over_allowance,
      })
    ));
};

module.exports = UserAllowance;
<|endoftext|>


<commit_before>import PatchBuffer from './patch-buffer';
import Hunk from './hunk';
import File, {nullFile} from './file';
import Patch, {TOO_LARGE, EXPANDED} from './patch';
import {Unchanged, Addition, Deletion, NoNewline} from './region';
import FilePatch from './file-patch';
import MultiFilePatch from './multi-file-patch';

export const DEFAULT_OPTIONS = {
  // Number of lines after which we consider the diff "large"
  // TODO: Set this based on performance measurements
  largeDiffThreshold: 100,

  // Map of file path (relative to repository root) to Patch render status (EXPANDED, COLLAPSED, TOO_LARGE)
  renderStatusOverrides: {},
};

export function buildFilePatch(diffs, options) {
  const opts = {...DEFAULT_OPTIONS, ...options};

  const patchBuffer = new PatchBuffer();

  let filePatch;
  if (diffs.length === 0) {
    filePatch = emptyDiffFilePatch();
  } else if (diffs.length === 1) {
    filePatch = singleDiffFilePatch(diffs[0], patchBuffer, opts);
  } else if (diffs.length === 2) {
    filePatch = dualDiffFilePatch(diffs[0], diffs[1], patchBuffer, opts);
  } else {
    throw new Error(`Unexpected number of diffs: ${diffs.length}`);
  }

  return new MultiFilePatch({patchBuffer, filePatches: [filePatch]});
}

export function buildMultiFilePatch(diffs, options) {
  const opts = {...DEFAULT_OPTIONS, ...options};

  const patchBuffer = new PatchBuffer();
  const byPath = new Map();
  const actions = [];

  let index = 0;
  for (const diff of diffs) {
    const thePath = diff.oldPath || diff.newPath;

    if (diff.status === 'added' || diff.status === 'deleted') {
      // Potential paired diff. Either a symlink deletion + content addition or a symlink addition +
      // content deletion.
      const otherHalf = byPath.get(thePath);
      if (otherHalf) {
        // The second half. Complete the paired diff, or fail if they have unexpected statuses or modes.
        const [otherDiff, otherIndex] = otherHalf;
        actions[otherIndex] = () => dualDiffFilePatch(diff, otherDiff, patchBuffer, opts);
        byPath.delete(thePath);
      } else {
        // The first half we've seen.
        byPath.set(thePath, [diff, index]);
        index++;
      }
    } else {
      actions[index] = () => singleDiffFilePatch(diff, patchBuffer, opts);
      index++;
    }
  }

  // Populate unpaired diffs that looked like they could be part of a pair, but weren't.
  for (const [unpairedDiff, originalIndex] of byPath.values()) {
    actions[originalIndex] = () => singleDiffFilePatch(unpairedDiff, patchBuffer, opts);
  }

  const filePatches = actions.map(action => action());
  return new MultiFilePatch({patchBuffer, filePatches});
}

function emptyDiffFilePatch() {
  return FilePatch.createNull();
}

function singleDiffFilePatch(diff, patchBuffer, opts) {
  const wasSymlink = diff.oldMode === File.modes.SYMLINK;
  const isSymlink = diff.newMode === File.modes.SYMLINK;

  let oldSymlink = null;
  let newSymlink = null;
  if (wasSymlink && !isSymlink) {
    oldSymlink = diff.hunks[0].lines[0].slice(1);
  } else if (!wasSymlink && isSymlink) {
    newSymlink = diff.hunks[0].lines[0].slice(1);
  } else if (wasSymlink && isSymlink) {
    oldSymlink = diff.hunks[0].lines[0].slice(1);
    newSymlink = diff.hunks[0].lines[2].slice(1);
  }

  const oldFile = diff.oldPath !== null || diff.oldMode !== null
    ? new File({path: diff.oldPath, mode: diff.oldMode, symlink: oldSymlink})
    : nullFile;
  const newFile = diff.newPath !== null || diff.newMode !== null
    ? new File({path: diff.newPath, mode: diff.newMode, symlink: newSymlink})
    : nullFile;

  const renderStatusOverride =
    (oldFile.isPresent() && opts.renderStatusOverrides[oldFile.getPath()]) ||
    (newFile.isPresent() && opts.renderStatusOverrides[newFile.getPath()]) ||
    undefined;

  const renderStatus = renderStatusOverride ||
    (isDiffLarge([diff], opts) && TOO_LARGE) ||
    EXPANDED;

  if (renderStatus === TOO_LARGE) {
    const insertRow = patchBuffer.getBuffer().getLastRow();
    const patchMarker = patchBuffer.markPosition(
      Patch.layerName,
      [insertRow, 0],
      {invalidate: 'never', exclusive: false},
    );

    return FilePatch.createDelayedFilePatch(
      oldFile, newFile, patchMarker, TOO_LARGE,
      () => {
        const [hunks] = buildHunks(diff, patchBuffer, insertRow, patchMarker);
        return new Patch({status: diff.status, hunks, marker: patchMarker});
      },
    );
  } else {
    const [hunks, patchMarker] = buildHunks(diff, patchBuffer, patchBuffer.getBuffer().getLastRow());
    const patch = new Patch({status: diff.status, hunks, marker: patchMarker});

    return new FilePatch(oldFile, newFile, patch);
  }
}

function dualDiffFilePatch(diff1, diff2, patchBuffer, opts) {
  let modeChangeDiff, contentChangeDiff;
  if (diff1.oldMode === File.modes.SYMLINK || diff1.newMode === File.modes.SYMLINK) {
    modeChangeDiff = diff1;
    contentChangeDiff = diff2;
  } else {
    modeChangeDiff = diff2;
    contentChangeDiff = diff1;
  }

  const filePath = contentChangeDiff.oldPath || contentChangeDiff.newPath;
  const symlink = modeChangeDiff.hunks[0].lines[0].slice(1);

  let status;
  let oldMode, newMode;
  let oldSymlink = null;
  let newSymlink = null;
  if (modeChangeDiff.status === 'added') {
    // contents were deleted and replaced with symlink
    status = 'deleted';
    oldMode = contentChangeDiff.oldMode;
    newMode = modeChangeDiff.newMode;
    newSymlink = symlink;
  } else if (modeChangeDiff.status === 'deleted') {
    // contents were added after symlink was deleted
    status = 'added';
    oldMode = modeChangeDiff.oldMode;
    oldSymlink = symlink;
    newMode = contentChangeDiff.newMode;
  } else {
    throw new Error(`Invalid mode change diff status: ${modeChangeDiff.status}`);
  }

  const oldFile = new File({path: filePath, mode: oldMode, symlink: oldSymlink});
  const newFile = new File({path: filePath, mode: newMode, symlink: newSymlink});

  if (isDiffLarge([diff1, diff2], opts)) {
    // TODO: do something with large diff too
  }

  const [hunks, patchMarker] = buildHunks(contentChangeDiff, patchBuffer, patchBuffer.getBuffer().getLastRow());
  const patch = new Patch({status, hunks, marker: patchMarker});

  return new FilePatch(oldFile, newFile, patch);
}

const CHANGEKIND = {
  '+': Addition,
  '-': Deletion,
  ' ': Unchanged,
  '\\': NoNewline,
};

function buildHunks(diff, patchBuffer, insertRow) {
  const inserter = patchBuffer.createInserterAt([insertRow, 0]);

  let patchMarker = null;
  const hunks = [];
  inserter.markWhile(Patch.layerName, () => {
    for (const rawHunk of diff.hunks) {
      const regions = [];

      inserter.markWhile(Hunk.layerName, () => {
        let currentRegionText = '';
        let CurrentRegionKind = null;

        function finishRegion() {
          if (currentRegionText === '' || CurrentRegionKind === null) {
            return;
          }

          inserter.insertMarked(currentRegionText, CurrentRegionKind.layerName, {
            invalidate: 'never',
            exclusive: false,
            callback: regionMarker => { regions.push(new CurrentRegionKind(regionMarker)); },
          });
        }

        for (const rawLine of rawHunk.lines) {
          const NextRegionKind = CHANGEKIND[rawLine[0]];
          if (NextRegionKind === undefined) {
            throw new Error(`Unknown diff status character: "${rawLine[0]}"`);
          }
          const nextLine = rawLine.slice(1) + '\n';

          if (NextRegionKind === CurrentRegionKind) {
            currentRegionText += nextLine;
            continue;
          } else {
            finishRegion();

            CurrentRegionKind = NextRegionKind;
            currentRegionText = nextLine;
          }
        }
        finishRegion();
      }, {
        invalidate: 'never',
        exclusive: false,
        callback: hunkMarker => {
          hunks.push(new Hunk({
            oldStartRow: rawHunk.oldStartLine,
            newStartRow: rawHunk.newStartLine,
            oldRowCount: rawHunk.oldLineCount,
            newRowCount: rawHunk.newLineCount,
            sectionHeading: rawHunk.heading,
            marker: hunkMarker,
            regions,
          }));
        },
      });
    }
  }, {
    invalidate: 'never',
    exclusive: false,
    callback: marker => { patchMarker = marker; },
  });
  inserter.apply();

  return [hunks, patchMarker];
}

function isDiffLarge(diffs, opts) {
  const size = diffs.reduce((diffSizeCounter, diff) => {
    return diffSizeCounter + diff.hunks.reduce((hunkSizeCounter, hunk) => {
      return hunkSizeCounter + hunk.lines.length;
    }, 0);
  }, 0);

  return size > opts.largeDiffThreshold;
}
<commit_msg>Insert newlines *between* consecutive patches, hunks, regions<commit_after>import PatchBuffer from './patch-buffer';
import Hunk from './hunk';
import File, {nullFile} from './file';
import Patch, {TOO_LARGE, EXPANDED} from './patch';
import {Unchanged, Addition, Deletion, NoNewline} from './region';
import FilePatch from './file-patch';
import MultiFilePatch from './multi-file-patch';

export const DEFAULT_OPTIONS = {
  // Number of lines after which we consider the diff "large"
  // TODO: Set this based on performance measurements
  largeDiffThreshold: 100,

  // Map of file path (relative to repository root) to Patch render status (EXPANDED, COLLAPSED, TOO_LARGE)
  renderStatusOverrides: {},
};

export function buildFilePatch(diffs, options) {
  const opts = {...DEFAULT_OPTIONS, ...options};

  const patchBuffer = new PatchBuffer();

  let filePatch;
  if (diffs.length === 0) {
    filePatch = emptyDiffFilePatch();
  } else if (diffs.length === 1) {
    filePatch = singleDiffFilePatch(diffs[0], patchBuffer, opts);
  } else if (diffs.length === 2) {
    filePatch = dualDiffFilePatch(diffs[0], diffs[1], patchBuffer, opts);
  } else {
    throw new Error(`Unexpected number of diffs: ${diffs.length}`);
  }

  return new MultiFilePatch({patchBuffer, filePatches: [filePatch]});
}

export function buildMultiFilePatch(diffs, options) {
  const opts = {...DEFAULT_OPTIONS, ...options};

  const patchBuffer = new PatchBuffer();
  const byPath = new Map();
  const actions = [];

  let index = 0;
  for (const diff of diffs) {
    const thePath = diff.oldPath || diff.newPath;

    if (diff.status === 'added' || diff.status === 'deleted') {
      // Potential paired diff. Either a symlink deletion + content addition or a symlink addition +
      // content deletion.
      const otherHalf = byPath.get(thePath);
      if (otherHalf) {
        // The second half. Complete the paired diff, or fail if they have unexpected statuses or modes.
        const [otherDiff, otherIndex] = otherHalf;
        actions[otherIndex] = () => dualDiffFilePatch(diff, otherDiff, patchBuffer, opts);
        byPath.delete(thePath);
      } else {
        // The first half we've seen.
        byPath.set(thePath, [diff, index]);
        index++;
      }
    } else {
      actions[index] = () => singleDiffFilePatch(diff, patchBuffer, opts);
      index++;
    }
  }

  // Populate unpaired diffs that looked like they could be part of a pair, but weren't.
  for (const [unpairedDiff, originalIndex] of byPath.values()) {
    actions[originalIndex] = () => singleDiffFilePatch(unpairedDiff, patchBuffer, opts);
  }

  const filePatches = actions.map(action => action());
  return new MultiFilePatch({patchBuffer, filePatches});
}

function emptyDiffFilePatch() {
  return FilePatch.createNull();
}

function singleDiffFilePatch(diff, patchBuffer, opts) {
  const wasSymlink = diff.oldMode === File.modes.SYMLINK;
  const isSymlink = diff.newMode === File.modes.SYMLINK;

  let oldSymlink = null;
  let newSymlink = null;
  if (wasSymlink && !isSymlink) {
    oldSymlink = diff.hunks[0].lines[0].slice(1);
  } else if (!wasSymlink && isSymlink) {
    newSymlink = diff.hunks[0].lines[0].slice(1);
  } else if (wasSymlink && isSymlink) {
    oldSymlink = diff.hunks[0].lines[0].slice(1);
    newSymlink = diff.hunks[0].lines[2].slice(1);
  }

  const oldFile = diff.oldPath !== null || diff.oldMode !== null
    ? new File({path: diff.oldPath, mode: diff.oldMode, symlink: oldSymlink})
    : nullFile;
  const newFile = diff.newPath !== null || diff.newMode !== null
    ? new File({path: diff.newPath, mode: diff.newMode, symlink: newSymlink})
    : nullFile;

  const renderStatusOverride =
    (oldFile.isPresent() && opts.renderStatusOverrides[oldFile.getPath()]) ||
    (newFile.isPresent() && opts.renderStatusOverrides[newFile.getPath()]) ||
    undefined;

  const renderStatus = renderStatusOverride ||
    (isDiffLarge([diff], opts) && TOO_LARGE) ||
    EXPANDED;

  if (renderStatus === TOO_LARGE) {
    const insertRow = patchBuffer.getBuffer().getLastRow();
    const patchMarker = patchBuffer.markPosition(
      Patch.layerName,
      [insertRow, 0],
      {invalidate: 'never', exclusive: false},
    );

    return FilePatch.createDelayedFilePatch(
      oldFile, newFile, patchMarker, TOO_LARGE,
      () => {
        const [hunks] = buildHunks(diff, patchBuffer, insertRow, patchMarker);
        return new Patch({status: diff.status, hunks, marker: patchMarker});
      },
    );
  } else {
    const [hunks, patchMarker] = buildHunks(diff, patchBuffer, patchBuffer.getBuffer().getLastRow());
    const patch = new Patch({status: diff.status, hunks, marker: patchMarker});

    return new FilePatch(oldFile, newFile, patch);
  }
}

function dualDiffFilePatch(diff1, diff2, patchBuffer, opts) {
  let modeChangeDiff, contentChangeDiff;
  if (diff1.oldMode === File.modes.SYMLINK || diff1.newMode === File.modes.SYMLINK) {
    modeChangeDiff = diff1;
    contentChangeDiff = diff2;
  } else {
    modeChangeDiff = diff2;
    contentChangeDiff = diff1;
  }

  const filePath = contentChangeDiff.oldPath || contentChangeDiff.newPath;
  const symlink = modeChangeDiff.hunks[0].lines[0].slice(1);

  let status;
  let oldMode, newMode;
  let oldSymlink = null;
  let newSymlink = null;
  if (modeChangeDiff.status === 'added') {
    // contents were deleted and replaced with symlink
    status = 'deleted';
    oldMode = contentChangeDiff.oldMode;
    newMode = modeChangeDiff.newMode;
    newSymlink = symlink;
  } else if (modeChangeDiff.status === 'deleted') {
    // contents were added after symlink was deleted
    status = 'added';
    oldMode = modeChangeDiff.oldMode;
    oldSymlink = symlink;
    newMode = contentChangeDiff.newMode;
  } else {
    throw new Error(`Invalid mode change diff status: ${modeChangeDiff.status}`);
  }

  const oldFile = new File({path: filePath, mode: oldMode, symlink: oldSymlink});
  const newFile = new File({path: filePath, mode: newMode, symlink: newSymlink});

  if (isDiffLarge([diff1, diff2], opts)) {
    // TODO: do something with large diff too
  }

  const [hunks, patchMarker] = buildHunks(contentChangeDiff, patchBuffer, patchBuffer.getBuffer().getLastRow());
  const patch = new Patch({status, hunks, marker: patchMarker});

  return new FilePatch(oldFile, newFile, patch);
}

const CHANGEKIND = {
  '+': Addition,
  '-': Deletion,
  ' ': Unchanged,
  '\\': NoNewline,
};

function buildHunks(diff, patchBuffer, insertRow) {
  const inserter = patchBuffer.createInserterAt([insertRow, 0]);

  let patchMarker = null;
  let firstHunk = true;
  const hunks = [];

  // Separate multiple patches on the same buffer with an unmarked newline
  if (patchBuffer.getBuffer().getLength() > 0) {
    console.log('inserting newline to separate Patches');
    inserter.insert('\n');
  }

  inserter.markWhile(Patch.layerName, () => {
    for (const rawHunk of diff.hunks) {
      let firstRegion = true;
      const regions = [];

      // Separate hunks with an unmarked newline
      if (firstHunk) {
        firstHunk = false;
      } else {
        console.log('inserting newline to separate Hunks');
        inserter.insert('\n');
      }

      inserter.markWhile(Hunk.layerName, () => {
        let currentRegionText = '';
        let CurrentRegionKind = null;

        function finishRegion() {
          if (currentRegionText === '' || CurrentRegionKind === null) {
            return;
          }

          // Separate regions with an unmarked newline
          if (firstRegion) {
            firstRegion = false;
          } else {
            console.log('inserting newline to separate Regions');
            inserter.insert('\n');
          }

          inserter.insertMarked(currentRegionText, CurrentRegionKind.layerName, {
            invalidate: 'never',
            exclusive: false,
            callback: regionMarker => { regions.push(new CurrentRegionKind(regionMarker)); },
          });
        }

        for (const rawLine of rawHunk.lines) {
          const NextRegionKind = CHANGEKIND[rawLine[0]];
          if (NextRegionKind === undefined) {
            throw new Error(`Unknown diff status character: "${rawLine[0]}"`);
          }
          const nextLine = rawLine.slice(1);

          if (NextRegionKind === CurrentRegionKind) {
            if (currentRegionText !== '') {
              currentRegionText += '\n';
            }
            currentRegionText += nextLine;
            continue;
          } else {
            finishRegion();

            CurrentRegionKind = NextRegionKind;
            currentRegionText = nextLine;
          }
        }
        finishRegion();
      }, {
        invalidate: 'never',
        exclusive: false,
        callback: hunkMarker => {
          hunks.push(new Hunk({
            oldStartRow: rawHunk.oldStartLine,
            newStartRow: rawHunk.newStartLine,
            oldRowCount: rawHunk.oldLineCount,
            newRowCount: rawHunk.newLineCount,
            sectionHeading: rawHunk.heading,
            marker: hunkMarker,
            regions,
          }));
        },
      });
    }
  }, {
    invalidate: 'never',
    exclusive: false,
    callback: marker => { patchMarker = marker; },
  });
  inserter.apply();

  return [hunks, patchMarker];
}

function isDiffLarge(diffs, opts) {
  const size = diffs.reduce((diffSizeCounter, diff) => {
    return diffSizeCounter + diff.hunks.reduce((hunkSizeCounter, hunk) => {
      return hunkSizeCounter + hunk.lines.length;
    }, 0);
  }, 0);

  return size > opts.largeDiffThreshold;
}
<|endoftext|>


<commit_before><commit_msg>[ADD] web: make export FAYT also match technical names<commit_after><|endoftext|>


<commit_before>Meteor.startup(function () {
  Session.set("emailNotSaved",false);
});

Meteor.subscribe('files');

// curly quotes break some links so convert them to straight quotes
removeCurlyQuotes = function (str) {
  var goodQuotes = str
  .replace(/[\u2018\u2019]/g, "'")
  .replace(/[\u201C\u201D]/g, '"');
  return goodQuotes;
};

urlEncodeQuotes = function (str) {
  return str.replace(/['""]/g, function(c) {
    return '%' + c.charCodeAt(0).toString(16);
  });
};

// add a check to see if the user wants to save the page before leaving
window.onbeforeunload = function () {
  if (Session.get("emailNotSaved")) {
    return "This email hasn't been saved.";
  } else if (Session.get("pageNotSaved")) {
    return "This page hasn't been saved.";
  } else { return null; }
};

// GLOBAL HELPERS

/* Set the name(s) of people who have access to
  - Settings: set the AK and SP auth information
  - Admin Deleted Pages: actually delete files from the database
*/
UI.registerHelper("isAdmin", function() {
  var admins = ['Jin Ding'];

  return Meteor.user() && admins.indexOf(Meteor.user().profile.name) >= 0;
});

// Return the type of mailing
UI.registerHelper("wrapperStyle", function(str) {
  return Session.equals('templateChooser', str) ? "true" : "";
});

UI.registerHelper("display_setting", function(selection) {
  return Session.equals("display",selection);
});

UI.registerHelper("getValue", function(value) {
  return Session.get(value);
});

UI.registerHelper("highlight", function(value) {
  return Session.get(value) ? null : 'unchanged';
});

UI.registerHelper("sigFirstName", function() {
  return Session.get("signature").split(' ')[0];
});

// return the HTML code of the template being referenced
UI.registerHelper("unformattedHtml", function(wrapper) {
  var div = document.createElement('div');
  switch (wrapper) {
    case "blank": UI.insert(UI.render(Template.blankEmailWrapperBody), div); break;
    case "call": UI.insert(UI.render(Template.callEmailWrapperBody), div); break;
    case "event": UI.insert(UI.render(Template.eventEmailWrapperBody), div); break;
    case "mobilize": UI.insert(UI.render(Template.mobilizeEmailWrapperBody), div); break;
    case "petition": UI.insert(UI.render(Template.petitionEmailWrapperBody), div); break;
    case "publicComment": UI.insert(UI.render(Template.publicCommentEmailWrapperBody), div); break;
    case "takeAction": UI.insert(UI.render(Template.takeActionEmailWrapperBody), div); break;
    case "superpacFundraiser": UI.insert(UI.render(Template.superpacFundraiserEmailWrapperBody), div); break;
    default: return "unformatted html not yet set";
  }
  return div.innerHTML;
});

// Markdown converter into HTML
UI.registerHelper("htmlFromMarkdown", function(field) {
  var converter = new Showdown.converter();
  return converter.makeHtml(Session.get(field));
});

// Strip out backslash from Markdown for certain fields
UI.registerHelper("removeBackslash", function(field) {
  var str = Session.get(field);
  return str.replace(/\\/g,'');
});

// used in the file list pages for "last saved" dates
UI.registerHelper("prettifyDate", function(d) {
  if (d) {
    var a_p = "";
    var curr_hour = d.getHours();
    var curr_min = d.getMinutes();
    var pad = function (x) {
      return (x < 10 ? '0' : '') + x;
    };

    if (curr_hour < 12) { a_p = "AM";}
      else { a_p = "PM"; }
    if (curr_hour == 0) { curr_hour = 12; }
    if (curr_hour > 12) { curr_hour = curr_hour - 12; }

    var str = [d.getMonth()+1,d.getDate(),d.getFullYear()-2000].join('/') +
      " " + curr_hour + ":" + pad(curr_min) + " " + a_p;
    return str;
  } else { return ''; }
});

UI.registerHelper("headlineButtonText", function() {
  switch (Session.get("fileSort")) {
    case 'headlineAsc': return "▲";
    case 'headlineDesc': return '▼';
    default: return '—';
  }
});

UI.registerHelper("typeButtonText", function() {
  switch (Session.get("fileSort")) {
    case 'typeAsc': return "▲";
    case 'typeDesc': return '▼';
    default: return '—';
  }
});

UI.registerHelper("createdByButtonText", function() {
  switch (Session.get("fileSort")) {
    case 'createdByAsc': return "▲";
    case 'createdByDesc': return '▼';
    default: return '—';
  }
});

UI.registerHelper("savedByButtonText", function() {
  switch (Session.get("fileSort")) {
    case 'savedByAsc': return "▲";
    case 'savedByDesc': return '▼';
    default: return '—';
  }
});

UI.registerHelper("savedAtButtonText", function() {
  switch (Session.get("fileSort")) {
    case 'savedAtAsc': return "▲";
    case 'savedAtDesc': return '▼';
    default: return '—';
  }
});

UI.registerHelper("belongsToUser", function(name) {
  return Meteor.user() && Meteor.user().profile.name === name;
});

UI.registerHelper("isNotEvent", function(pageType) {
  return pageType !== 'event';
});
<commit_msg>a little more cleanup and documentation<commit_after>Meteor.startup(function () {
  Session.set("emailNotSaved",false);
});

// subscribe to the files collection (ie database)
Meteor.subscribe('files');

// curly quotes break some links so convert them to straight quotes
removeCurlyQuotes = function (str) {
  var goodQuotes = str
  .replace(/[\u2018\u2019]/g, "'")
  .replace(/[\u201C\u201D]/g, '"');
  return goodQuotes;
};

urlEncodeQuotes = function (str) {
  return str.replace(/['""]/g, function(c) {
    return '%' + c.charCodeAt(0).toString(16);
  });
};

// add a check to see if the user wants to save the page before leaving
window.onbeforeunload = function () {
  if (Session.get("emailNotSaved")) {
    return "This email hasn't been saved.";
  } else if (Session.get("pageNotSaved")) {
    return "This page hasn't been saved.";
  } else { return null; }
};

// GLOBAL HELPERS

/*****************************************************
  Set the name(s) of people who have access to
  - Settings: set the AK and SP auth information
  - Admin Deleted Pages: actually delete files from the database
******************************************************/
UI.registerHelper("isAdmin", function() {
  var admins = ['Jin Ding'];
  return Meteor.user() && admins.indexOf(Meteor.user().profile.name) >= 0;
});

// Return the type of mailing
UI.registerHelper("wrapperStyle", function(str) {
  return Session.equals('templateChooser', str) ? "true" : "";
});

UI.registerHelper("display_setting", function(selection) {
  return Session.equals("display",selection);
});

UI.registerHelper("getValue", function(value) {
  return Session.get(value);
});

UI.registerHelper("highlight", function(value) {
  return Session.get(value) ? null : 'unchanged';
});

UI.registerHelper("sigFirstName", function() {
  return Session.get("signature").split(' ')[0];
});

// return the HTML code of the template being referenced
UI.registerHelper("unformattedHtml", function(wrapper) {
  var div = document.createElement('div');
  switch (wrapper) {
    case "blank": UI.insert(UI.render(Template.blankEmailWrapperBody), div); break;
    case "call": UI.insert(UI.render(Template.callEmailWrapperBody), div); break;
    case "event": UI.insert(UI.render(Template.eventEmailWrapperBody), div); break;
    case "mobilize": UI.insert(UI.render(Template.mobilizeEmailWrapperBody), div); break;
    case "petition": UI.insert(UI.render(Template.petitionEmailWrapperBody), div); break;
    case "publicComment": UI.insert(UI.render(Template.publicCommentEmailWrapperBody), div); break;
    case "takeAction": UI.insert(UI.render(Template.takeActionEmailWrapperBody), div); break;
    case "superpacFundraiser": UI.insert(UI.render(Template.superpacFundraiserEmailWrapperBody), div); break;
    default: return "unformatted html not yet set";
  }
  return div.innerHTML;
});

// Markdown converter into HTML
UI.registerHelper("htmlFromMarkdown", function(field) {
  var converter = new Showdown.converter();
  return converter.makeHtml(Session.get(field));
});

// Strip out backslash from Markdown for certain fields
UI.registerHelper("removeBackslash", function(field) {
  var str = Session.get(field);
  return str.replace(/\\/g,'');
});

// used in the file list pages for "last saved" dates
UI.registerHelper("prettifyDate", function(d) {
  if (d) {
    var a_p = "";
    var curr_hour = d.getHours();
    var curr_min = d.getMinutes();
    var pad = function (x) {
      return (x < 10 ? '0' : '') + x;
    };

    if (curr_hour < 12) { a_p = "AM";}
      else { a_p = "PM"; }
    if (curr_hour == 0) { curr_hour = 12; }
    if (curr_hour > 12) { curr_hour = curr_hour - 12; }

    var str = [d.getMonth()+1,d.getDate(),d.getFullYear()-2000].join('/') +
      " " + curr_hour + ":" + pad(curr_min) + " " + a_p;
    return str;
  } else { return ''; }
});

UI.registerHelper("headlineButtonText", function() {
  switch (Session.get("fileSort")) {
    case 'headlineAsc': return "▲";
    case 'headlineDesc': return '▼';
    default: return '—';
  }
});

UI.registerHelper("typeButtonText", function() {
  switch (Session.get("fileSort")) {
    case 'typeAsc': return "▲";
    case 'typeDesc': return '▼';
    default: return '—';
  }
});

UI.registerHelper("createdByButtonText", function() {
  switch (Session.get("fileSort")) {
    case 'createdByAsc': return "▲";
    case 'createdByDesc': return '▼';
    default: return '—';
  }
});

UI.registerHelper("savedByButtonText", function() {
  switch (Session.get("fileSort")) {
    case 'savedByAsc': return "▲";
    case 'savedByDesc': return '▼';
    default: return '—';
  }
});

UI.registerHelper("savedAtButtonText", function() {
  switch (Session.get("fileSort")) {
    case 'savedAtAsc': return "▲";
    case 'savedAtDesc': return '▼';
    default: return '—';
  }
});

UI.registerHelper("belongsToUser", function(name) {
  return Meteor.user() && Meteor.user().profile.name === name;
});

UI.registerHelper("isNotEvent", function(pageType) {
  return pageType !== 'event';
});
<|endoftext|>


<commit_before>import namehash from 'eth-ens-namehash';

/*global web3, EmbarkJS*/
let __embarkENS = {};

// resolver interface
__embarkENS.resolverInterface = [
  {
    "constant": true,
    "inputs": [
      {
        "name": "node",
        "type": "bytes32"
      }
    ],
    "name": "addr",
    "outputs": [
      {
        "name": "",
        "type": "address"
      }
    ],
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [
      {
        "name": "node",
        "type": "bytes32"
      }
    ],
    "name": "content",
    "outputs": [
      {
        "name": "",
        "type": "bytes32"
      }
    ],
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [
      {
        "name": "node",
        "type": "bytes32"
      }
    ],
    "name": "name",
    "outputs": [
      {
        "name": "",
        "type": "string"
      }
    ],
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [
      {
        "name": "node",
        "type": "bytes32"
      },
      {
        "name": "kind",
        "type": "bytes32"
      }
    ],
    "name": "has",
    "outputs": [
      {
        "name": "",
        "type": "bool"
      }
    ],
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      {
        "name": "node",
        "type": "bytes32"
      },
      {
        "name": "addr",
        "type": "address"
      }
    ],
    "name": "setAddr",
    "outputs": [],
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      {
        "name": "node",
        "type": "bytes32"
      },
      {
        "name": "hash",
        "type": "bytes32"
      }
    ],
    "name": "setContent",
    "outputs": [],
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      {
        "name": "node",
        "type": "bytes32"
      },
      {
        "name": "name",
        "type": "string"
      }
    ],
    "name": "setName",
    "outputs": [],
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [
      {
        "name": "node",
        "type": "bytes32"
      },
      {
        "name": "contentType",
        "type": "uint256"
      }
    ],
    "name": "ABI",
    "outputs": [
      {
        "name": "",
        "type": "uint256"
      },
      {
        "name": "",
        "type": "bytes"
      }
    ],
    "payable": false,
    "type": "function"
  }
];

__embarkENS.registryAddresses = {
  // Mainnet
  "1": "0x314159265dd8dbb310642f98f50c066173c1259b",
  // Ropsten
  "3": "0x112234455c3a32fd11230c42e7bccd4a84e02010",
  // Rinkeby
  "4": "0xe7410170f87102DF0055eB195163A03B7F2Bff4A"
};

__embarkENS.setProvider = function () {
  const self = this;
  // get network id and then assign ENS contract based on that
  let registryAddresses = this.registryAddresses;
  this.ens = null;
  web3.eth.net.getId().then(id => {
    if (registryAddresses[id] !== undefined) {
      EmbarkJS.onReady(() => {
        self.ens = new EmbarkJS.Contract({abi: self.registryInterface, address: registryAddresses[id]});
      });
    }
    // todo: deploy at this point
  }).catch(e => {
    if (e.message.indexOf('Provider not set or invalid') > -1) {
      console.warn('ENS is not available in this chain');
      return;
    }
    console.error(e);
  });
};

__embarkENS.resolve = function (name) {
  const self = this;

  if (self.ens === undefined) return;

  let node = namehash.hash(name);

  return self.ens.methods.resolver(node).call().then((resolverAddress) => {
    let resolverContract = new EmbarkJS.Contract({abi: self.resolverInterface, address: resolverAddress});
    return resolverContract.methods.addr(node).call();
  }).then((addr) => {
    return addr;
  }).catch((err) => {
    if (err == 'Couldn\'t decode addr from ABI: 0x') {
      console.log(name + " is not registered");
      return "0x";
    }
    return err;
  });
};

__embarkENS.lookup = function (address) {
  const self = this;

  if (!self.ens) {
    console.log("ENS provider not set. Exiting.");
    return;
  }
  if (address.startsWith("0x")) address = address.slice(2);

  let node = namehash.hash(address.toLowerCase() + ".addr.reverse");

  return self.ens.methods.resolver(node).call().then((resolverAddress) => {
    let resolverContract = new EmbarkJS.Contract({abi: self.resolverInterface, address: resolverAddress});
    return resolverContract.methods.name(node).call();
  }).catch((err) => {
    if (err == 'ERROR: The returned value is not a convertible string: 0x0') {
      console.log('Address does not resolve to name. Try syncing chain.');
      return "";
    }
    return err;
  });
};
<commit_msg>small correction<commit_after>import namehash from 'eth-ens-namehash';

/*global web3, EmbarkJS*/
let __embarkENS = {};

// resolver interface
__embarkENS.resolverInterface = [
  {
    "constant": true,
    "inputs": [
      {
        "name": "node",
        "type": "bytes32"
      }
    ],
    "name": "addr",
    "outputs": [
      {
        "name": "",
        "type": "address"
      }
    ],
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [
      {
        "name": "node",
        "type": "bytes32"
      }
    ],
    "name": "content",
    "outputs": [
      {
        "name": "",
        "type": "bytes32"
      }
    ],
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [
      {
        "name": "node",
        "type": "bytes32"
      }
    ],
    "name": "name",
    "outputs": [
      {
        "name": "",
        "type": "string"
      }
    ],
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [
      {
        "name": "node",
        "type": "bytes32"
      },
      {
        "name": "kind",
        "type": "bytes32"
      }
    ],
    "name": "has",
    "outputs": [
      {
        "name": "",
        "type": "bool"
      }
    ],
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      {
        "name": "node",
        "type": "bytes32"
      },
      {
        "name": "addr",
        "type": "address"
      }
    ],
    "name": "setAddr",
    "outputs": [],
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      {
        "name": "node",
        "type": "bytes32"
      },
      {
        "name": "hash",
        "type": "bytes32"
      }
    ],
    "name": "setContent",
    "outputs": [],
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      {
        "name": "node",
        "type": "bytes32"
      },
      {
        "name": "name",
        "type": "string"
      }
    ],
    "name": "setName",
    "outputs": [],
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [
      {
        "name": "node",
        "type": "bytes32"
      },
      {
        "name": "contentType",
        "type": "uint256"
      }
    ],
    "name": "ABI",
    "outputs": [
      {
        "name": "",
        "type": "uint256"
      },
      {
        "name": "",
        "type": "bytes"
      }
    ],
    "payable": false,
    "type": "function"
  }
];

__embarkENS.registryAddresses = {
  // Mainnet
  "1": "0x314159265dd8dbb310642f98f50c066173c1259b",
  // Ropsten
  "3": "0x112234455c3a32fd11230c42e7bccd4a84e02010",
  // Rinkeby
  "4": "0xe7410170f87102DF0055eB195163A03B7F2Bff4A"
};

__embarkENS.setProvider = function () {
  const self = this;
  // get network id and then assign ENS contract based on that
  let registryAddresses = this.registryAddresses;
  this.ens = null;
  web3.eth.net.getId().then(id => {
    if (registryAddresses[id] !== undefined) {
      EmbarkJS.onReady(() => {
        self.ens = new EmbarkJS.Contract({abi: self.registryInterface, address: registryAddresses[id]});
      });
    }
    // todo: deploy at this point
  }).catch(e => {
    if (e.message.indexOf('Provider not set or invalid') > -1) {
      console.warn('ENS is not available in this chain');
      return;
    }
    console.error(e);
  });
};

__embarkENS.resolve = function (name) {
  const self = this;

  if (self.ens === undefined) return;

  let node = namehash.hash(name);

  return self.ens.methods.resolver(node).call().then((resolverAddress) => {
    let resolverContract = new EmbarkJS.Contract({abi: self.resolverInterface, address: resolverAddress});
    return resolverContract.methods.addr(node).call();
  }).then((addr) => {
    return addr;
  }).catch((err) => {
    if (err == 'Error: Couldn\'t decode address from ABI: 0x') {
      console.log(name + " is not registered");
      return "0x";
    }
    return err;
  });
};

__embarkENS.lookup = function (address) {
  const self = this;

  if (!self.ens) {
    console.log("ENS provider not set. Exiting.");
    return;
  }
  if (address.startsWith("0x")) address = address.slice(2);

  let node = namehash.hash(address.toLowerCase() + ".addr.reverse");

  return self.ens.methods.resolver(node).call().then((resolverAddress) => {
    let resolverContract = new EmbarkJS.Contract({abi: self.resolverInterface, address: resolverAddress});
    return resolverContract.methods.name(node).call();
  }).catch((err) => {
    if (err == 'ERROR: The returned value is not a convertible string: 0x0' || err == 'Error: Couldn\'t decode address from ABI: 0x') {
      console.log('Address does not resolve to name. Try syncing chain.');
      return "";
    }
    return err;
  });
};
<|endoftext|>


<commit_before>const utils = require('web3-utils');
const _ = require('underscore');

// generates random inputs based on the inputs of an ABI
class ContractFuzzer {
  constructor(embark) {
    //this.abi = abi;
    this.embark = embark;
    this.logger = embark.logger;
    this.events = embark.events;

    this.registerConsoleCommand();
  }

  // main function to call, takes in iteration number and a contract and returns a map object
  // composed of method names -> fuzzed inputs.
  generateFuzz(iterations, contract) {
    let fuzzMap = {};
    for (let i = 0; i < iterations; i++) contract.abiDefinition.filter((x) => x.inputs && x.inputs.length != 0).forEach((abiMethod) => {
          let name = abiMethod.type === "constructor" ? "constructor" : abiMethod.name;
          console.log("name");
          let inputTypes = abiMethod.inputs.map(input => input.type);
          console.log("INPUT TYPES:", inputTypes);
          let fuzzedInputs = _.map(inputTypes, this.getTypeFuzz.bind(this));
          console.log("FUZZED INPUTS:", fuzzedInputs);
          fuzzMap[name] = fuzzedInputs;
          console.log("FUZZ KEYS SO FAR:", Object.keys(fuzzMap));
          console.log("FUZZ VALS SO FAR:", Object.values(fuzzMap));
        });
    console.log("FUZZ:", Object.keys(fuzzMap), Object.values(fuzzMap));
    return fuzzMap;
  }

  getTypeFuzz(typeString) {
    const self = this;
    console.log("TYPESTRING:", typeString);
    // Group 0: uint256[3]
    // Group 1: uint256
    // Group 2: uint
    // Group 3: 256
    // Group 4: [3]
    // Group 5: 3
    let regexObj = typeString.match(/((bool|int|uint|bytes|string|address)([0-9]*)?)(\[([0-9]*)\])*$/);
    console.log("REGEX OBJ:", regexObj);
    let type = regexObj[1];
    console.log("type:", type);
    let kind = regexObj[2];
    console.log("kind:", kind);
    let size = regexObj[3];
    console.log("size:", size);
    let array = regexObj[4];
    console.log("array:", array);
    let arraySize = regexObj[5];
    console.log("array size:", arraySize);
    switch(true) {
      case array !== undefined: {
        // if it's a dynamic array pick a number between 1 and 256 for length of array
        let length = arraySize === undefined || arraySize === null || arraySize === '' ? Math.floor((Math.random() * 256) + 1)  : arraySize;
        console.log("LENGTH: ", length);
        return self.generateArrayOfType(length, type);
      }
      case kind == "bool":
        return self.generateRandomBool();
      case kind == "uint" || kind == "int":
        return self.generateRandomInt(size);
      case kind === "bytes" && size !== undefined:
        return self.generateRandomStaticBytes(size);
      case kind === "string" || kind === "bytes":
        return self.generateRandomDynamicType();
      case kind === "address":
        return self.generateRandomAddress();
      default:
        throw new Error("Couldn't find proper ethereum abi type");
    }
  }

  generateRandomBool() {
    return _.sample([true, false]);
  }

  generateArrayOfType(length, type) {
    var arr = [];
    for (var i = 0; i < length; i++) {
      arr.push(this.getTypeFuzz(type));
    }
    console.log("Final Array:", arr);
    return arr;
  }

  generateRandomDynamicType() {
    return Math.random().toString(36).slice(2);
  }

  generateRandomStaticBytes(size) {
    return utils.randomHex(size);
  }

  generateRandomInt(size) {
    return utils.toBN(utils.randomHex(size / 8));
  }

  generateRandomAddress() {
    return utils.randomHex(20);
  }

  registerConsoleCommand() {
    const self = this;
    self.embark.registerConsoleCommand((cmd, _options) => {
      let splitCmd = cmd.split(' ');
      let cmdName = splitCmd[0];
      let contractName = splitCmd[1];
      let iterations = splitCmd[2] === undefined ? 1 : splitCmd[2];
      if (cmdName === 'fuzz') {
        self.events.request('contracts:contract', contractName, (contract) => {
          self.logger.info("--  fuzzed vals for " + contractName);
             this.generateFuzz(iterations, contract);
        });
        return "";
        }
        return false;
    });
  }
}

module.exports = ContractFuzzer;
<commit_msg>convert console logs to embark logger and this baby is ready to rock<commit_after>const utils = require('web3-utils');
const _ = require('underscore');

// generates random inputs based on the inputs of an ABI
class ContractFuzzer {
  constructor(embark) {
    //this.abi = abi;
    this.embark = embark;
    this.logger = embark.logger;
    this.events = embark.events;

    this.registerConsoleCommand();
  }

  // main function to call, takes in iteration number and a contract and returns a map object
  // composed of method names -> fuzzed inputs.
  generateFuzz(iterations, contract) {
    const self = this;
    let fuzzMap = {};
    for (let i = 0; i < iterations; i++) contract.abiDefinition.filter((x) => x.inputs && x.inputs.length != 0).forEach((abiMethod) => {
          let name = abiMethod.type === "constructor" ? "constructor" : abiMethod.name;
          let inputTypes = abiMethod.inputs.map(input => input.type);
          let fuzzedInputs = _.map(inputTypes, this.getTypeFuzz.bind(this));
          fuzzMap[name] = fuzzedInputs;
        });
    for (let key in fuzzMap) self.logger.info(key + ":" + fuzzMap[key]);
    return fuzzMap;
  }

  getTypeFuzz(typeString) {
    const self = this;
    // Group 0: uint256[3]
    // Group 1: uint256
    // Group 2: uint
    // Group 3: 256
    // Group 4: [3]
    // Group 5: 3
    let regexObj = typeString.match(/((bool|int|uint|bytes|string|address)([0-9]*)?)(\[([0-9]*)\])*$/);
    let type = regexObj[1];
    let kind = regexObj[2];
    let size = regexObj[3];
    let array = regexObj[4];
    let arraySize = regexObj[5];
    switch(true) {
      case array !== undefined: {
        // if it's a dynamic array pick a number between 1 and 256 for length of array
        let length = arraySize === undefined || arraySize === null || arraySize === '' ? Math.floor((Math.random() * 256) + 1)  : arraySize;
        return self.generateArrayOfType(length, type);
      }
      case kind == "bool":
        return self.generateRandomBool();
      case kind == "uint" || kind == "int":
        return self.generateRandomInt(size);
      case kind === "bytes" && size !== undefined:
        return self.generateRandomStaticBytes(size);
      case kind === "string" || kind === "bytes":
        return self.generateRandomDynamicType();
      case kind === "address":
        return self.generateRandomAddress();
      default:
        throw new Error("Couldn't find proper ethereum abi type");
    }
  }

  generateRandomBool() {
    return _.sample([true, false]);
  }

  generateArrayOfType(length, type) {
    var arr = [];
    for (var i = 0; i < length; i++) arr.push(this.getTypeFuzz(type));
    return arr;
  }

  generateRandomDynamicType() {
    return Math.random().toString(36).slice(2);
  }

  generateRandomStaticBytes(size) {
    return utils.randomHex(size);
  }

  generateRandomInt(size) {
    return utils.toBN(utils.randomHex(size / 8));
  }

  generateRandomAddress() {
    return utils.randomHex(20);
  }

  registerConsoleCommand() {
    const self = this;
    self.embark.registerConsoleCommand((cmd, _options) => {
      let splitCmd = cmd.split(' ');
      let cmdName = splitCmd[0];
      let contractName = splitCmd[1];
      let iterations = splitCmd[2] === undefined ? 1 : splitCmd[2];
      if (cmdName === 'fuzz') {
        self.events.request('contracts:contract', contractName, (contract) => {
          self.logger.info("--  fuzzed vals for " + contractName);
             this.generateFuzz(iterations, contract);
        });
        return "";
        }
        return false;
    });
  }
}

module.exports = ContractFuzzer;
<|endoftext|>


<commit_before>/*
 * jsbyte: An interactive JS/HTML/CSS environment
 */

define([
  'config', 'utils', 'jquery', 'underscore',
  'backbone', 'cdn', 'dom', 'keys', 'mirrors', 'panels', 'templates'
],
function(
  config, utils, $, _,
  Backbone, cdn, dom, keys, mirrors, panels, templates
) {
  'use strict';

  var jsbyte = utils.module('jsbyte');

  jsbyte.App = Backbone.View.extend({
    template: 'app',
    el: '#jsbyte',

    initialize: function() {
      var that = this;

      // fetch the CDN package cache right away
      cdn.update_cache();

      this.render(function() {
        // init various components
        mirrors.init(this.$inputPanels.children('textarea'));
        panels.init(this.$panels);
        cdn.init_filter();

        this.register_keys();
        this.remove_splash();
      });
    },

    events: {
      'click #github': function(e) { window.open(config.github); }
    },

    // submit byte to the frame server
    run: function() {
      this.$input.submit();
    },

    register_keys: function() {
      var that = this;

      keys.register_handler({ ctrl: true, key: 'enter' }, function(e) {
        that.run();
      });

      // don't submit anything on input return
      this.$inputPanels.find('input').on('keypress keydown keyup', function(e) {
        if (e.keyCode === keys.key_code_for('enter')) { e.preventDefault(); }
      });

      keys.init();
    },

    remove_splash: function() {
      var that = this;
      _.delay(function() {
        $('#loading').transition({ 'opacity': '0' }, 500, function() {
          $(this).remove();

          that.$title.children('.text').transition({ 'opacity': 1 }, 'slow');
        });
      }, config.debug ? 0 : 1000);
    },

    render: function(callback) {
      templates.get(this.template, function(template) {
        var html = _.template(template, { frame: config.frame });
        this.$el.html(html);

        // cache elements to the Backbone View
        dom.cache(this, this.$el, {
          'by_id': ['title', 'markup', 'style', 'script', 'input', 'output'],
          'by_class': ['panel']
        });

        this.$inputPanels = this.$panels.not('iframe');

        if (_.isFunction(callback)) { callback.call(this); }
      }, this);

      return this;
    }
  });

  return jsbyte;
});
<commit_msg>don't submit form (run byte) when clicking an options button<commit_after>/*
 * jsbyte: An interactive JS/HTML/CSS environment
 */

define([
  'config', 'utils', 'jquery', 'underscore',
  'backbone', 'cdn', 'dom', 'keys', 'mirrors', 'panels', 'templates'
],
function(
  config, utils, $, _,
  Backbone, cdn, dom, keys, mirrors, panels, templates
) {
  'use strict';

  var jsbyte = utils.module('jsbyte');

  jsbyte.App = Backbone.View.extend({
    template: 'app',
    el: '#jsbyte',

    initialize: function() {
      var that = this;

      // fetch the CDN package cache right away
      cdn.update_cache();

      this.render(function() {
        // init various components
        mirrors.init(this.$inputPanels.children('textarea'));
        panels.init(this.$panels);
        cdn.init_filter();

        this.register_keys();
        this.remove_splash();
      });
    },

    events: {
      'click #github': function(e) { window.open(config.github); }
    },

    // submit byte to the frame server
    run: function() {
      this.$input.submit();
    },

    register_keys: function() {
      var that = this;

      keys.register_handler({ ctrl: true, key: 'enter' }, function(e) {
        that.run();
      });

      // don't submit anything on input return
      this.$inputPanels.find('input').on('keypress keydown keyup', function(e) {
        if (e.keyCode === keys.key_code_for('enter')) { e.preventDefault(); }
      });

      this.$inputPanels.find('button').on('click', function(e) {
        e.preventDefault();
      });

      keys.init();
    },

    remove_splash: function() {
      var that = this;
      _.delay(function() {
        $('#loading').transition({ 'opacity': '0' }, 500, function() {
          $(this).remove();

          that.$title.children('.text').transition({ 'opacity': 1 }, 'slow');
        });
      }, config.debug ? 0 : 1000);
    },

    render: function(callback) {
      templates.get(this.template, function(template) {
        var html = _.template(template, { frame: config.frame });
        this.$el.html(html);

        // cache elements to the Backbone View
        dom.cache(this, this.$el, {
          'by_id': ['title', 'markup', 'style', 'script', 'input', 'output'],
          'by_class': ['panel']
        });

        this.$inputPanels = this.$panels.not('iframe');

        if (_.isFunction(callback)) { callback.call(this); }
      }, this);

      return this;
    }
  });

  return jsbyte;
});
<|endoftext|>


<commit_before>// PairMatic - a real-time remote pair coordinating web app
// Copyright (C) 2012 - Glen E. Ivey
// https://github.com/gleneivey/pairmatic
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License version
// 3 as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program in the file COPYING and/or LICENSE. If not,
// see <http://www.gnu.org/licenses/>.

var pairingState = {};
var dragstartPosition = {};
var dragWatcher = null;

function initialPageRender(data) {
  var html = '';

  html += '
Reset
';
  html += '
';
  var pairs = (data.length / 2) + 2;
  for (var i=0; i < pairs; i++) {
    html += '
	'
    html +=     ' 	';
    html +=     '
';
  }
  html += '
\n';
  html += '';
  html += '';

  for (var i=0; i < data.length; i++) {
    var id=data[i][0], name=data[i][1], location=data[i][3], email=data[i][4];
    html += '
';

    if (email) {
      var h = /@/.test(email) ? hex_md5(email) : email;
      html+= '';
    }

    if (location) {
      html += '
' + location + '
';
    }

    html += '' + name + '
';
  }

  $('#content').html(html);

  resetPersonPositions();
  setupNoteIcons();
  $('.reset-button').click(function() {
    resetData();
    resetPersonPositions();
    socket.emit('reset', {});
  });

  $('body').droppable({
    drop: function(event, ui) {
      doUnpair(ui.draggable[0]);
    }
  });

  $('td').droppable({
    drop: function(event, ui) {
      var person = ui.draggable.get(0);
      if (putThisIntoThat(person.id, this.id)) {
	socket.emit('pair', {
	    person: person.id,
	    target: this.id
	});
      } else {
        person.style.top = dragstartPosition.top;
        person.style.left = dragstartPosition.left;
        sendUnpair(person);
      }
    }
  });
}

function setupNoteIcons() {
  $('.notes').html('');
  $('.notes img').click(function() {
    var td = $(this).parent();
    td.html('<textarea rows="4" />');
    var textarea = $('textarea', td);
    textarea.focus().change(sendNoteUpdate).keyup(sendNoteUpdate);
  });
}

function sendNoteUpdate(event, ui) {
  socket.emit('note', {
    target: $(this).parent().get(0).id,
    note: $(this).val()
  });
}

function setNote(target, note) {
  var td = $('#' + target);
  td.html('<textarea rows="4">' + note + '</textarea>');
}

function onDragStart(event, ui) {
  dragstartPosition = {
    top: ui.originalPosition.top,
    left: ui.originalPosition.left
  };

  var person = event.target;
  dragWatcher = setInterval(
      function() {
	socket.emit('move', {
	    person: person.id,
	    top: person.style.top,
	    left: person.style.left
	 });
      }, 70
  );
}

function onDragStop(event, ui) {
  clearInterval(dragWatcher);
  dragWatcher = null;
}

function resetData() {
  pairingState = {};
  dragstartPosition = {};
  setupNoteIcons();
}

function resetPersonPositions() {
  $('.food').draggable({
    start: onDragStart,
    stop: onDragStop
  });

  var x = 10;
  var y = 80;
  $('.person').draggable({
    start: onDragStart,
    stop: onDragStop
  }).each(function() {
    this.style.left = x;
    this.style.top = y;
    x += 90;
    if (x >= 300) {
      y += 90;
      x = 10;
    }
  });
}

function removeHashEntryByValue(h, v){
  for (var k in h) {
    if (h[k] == v) {
      delete h[k];
    }
  }
}

function putThisIntoThat(person, target) {
  if (pairingState[target]) {
    return false;
  }

  removeHashEntryByValue(pairingState, person);
  pairingState[target] = person;

  var td_position = $('#' + target).position();
  person = $('#' + person).get(0);
  person.style.top = td_position.top + 10;
  person.style.left = td_position.left + 15;

  return true;
}

function doUnpair(elem) {
  var id = elem.id;
  removeHashEntryByValue(pairingState, id);
  socket.emit('unpair', {
      person: id,
      top: elem.style.top,
      left: elem.style.left
   });
}

function doMove(data) {
  person = $('#' + data.person).get(0);
  person.style.top = data.top;
  person.style.left = data.left;
}

var socket = io.connect('/');
socket.on('init', function(data) {
  console.log(data);
  initialPageRender(data.personData);
  $.each(data['notes'], function(target, note) {
    setNote(target, note);
  });
  $.each(data['state'], function(target, person) {
    putThisIntoThat(person, target);
  });
});
socket.on('reset', function() {
  resetData();
  resetPersonPositions();
});
socket.on('pair', function(data) {
  putThisIntoThat(data.person, data.target);
});
socket.on('unpair', function(data) {
  removeHashEntryByValue(pairingState, data.person);
  doMove(data);
});
socket.on('move', function(data) {
  doMove(data);
});
socket.on('note', function(data) {
  setNote(data.target, data.note);
});
<commit_msg>log the initial data state on page load; allow manual capture of day's pair until we implement a database<commit_after>// PairMatic - a real-time remote pair coordinating web app
// Copyright (C) 2012 - Glen E. Ivey
// https://github.com/gleneivey/pairmatic
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License version
// 3 as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program in the file COPYING and/or LICENSE. If not,
// see <http://www.gnu.org/licenses/>.

var pairingState = {};
var dragstartPosition = {};
var dragWatcher = null;

function initialPageRender(data) {
  var html = '';

  html += '
Reset
';
  html += '
';
  var pairs = (data.length / 2) + 2;
  for (var i=0; i < pairs; i++) {
    html += '
	'
    html +=     ' 	';
    html +=     '
';
  }
  html += '
\n';
  html += '';
  html += '';

  for (var i=0; i < data.length; i++) {
    var id=data[i][0], name=data[i][1], location=data[i][3], email=data[i][4];
    html += '
';

    if (email) {
      var h = /@/.test(email) ? hex_md5(email) : email;
      html+= '';
    }

    if (location) {
      html += '
' + location + '
';
    }

    html += '' + name + '
';
  }

  $('#content').html(html);

  resetPersonPositions();
  setupNoteIcons();
  $('.reset-button').click(function() {
    resetData();
    resetPersonPositions();
    socket.emit('reset', {});
  });

  $('body').droppable({
    drop: function(event, ui) {
      doUnpair(ui.draggable[0]);
    }
  });

  $('td').droppable({
    drop: function(event, ui) {
      var person = ui.draggable.get(0);
      if (putThisIntoThat(person.id, this.id)) {
	socket.emit('pair', {
	    person: person.id,
	    target: this.id
	});
      } else {
        person.style.top = dragstartPosition.top;
        person.style.left = dragstartPosition.left;
        sendUnpair(person);
      }
    }
  });
}

function setupNoteIcons() {
  $('.notes').html('');
  $('.notes img').click(function() {
    var td = $(this).parent();
    td.html('<textarea rows="4" />');
    var textarea = $('textarea', td);
    textarea.focus().change(sendNoteUpdate).keyup(sendNoteUpdate);
  });
}

function sendNoteUpdate(event, ui) {
  socket.emit('note', {
    target: $(this).parent().get(0).id,
    note: $(this).val()
  });
}

function setNote(target, note) {
  var td = $('#' + target);
  td.html('<textarea rows="4">' + note + '</textarea>');
}

function onDragStart(event, ui) {
  dragstartPosition = {
    top: ui.originalPosition.top,
    left: ui.originalPosition.left
  };

  var person = event.target;
  dragWatcher = setInterval(
      function() {
	socket.emit('move', {
	    person: person.id,
	    top: person.style.top,
	    left: person.style.left
	 });
      }, 70
  );
}

function onDragStop(event, ui) {
  clearInterval(dragWatcher);
  dragWatcher = null;
}

function resetData() {
  pairingState = {};
  dragstartPosition = {};
  setupNoteIcons();
}

function resetPersonPositions() {
  $('.food').draggable({
    start: onDragStart,
    stop: onDragStop
  });

  var x = 10;
  var y = 80;
  $('.person').draggable({
    start: onDragStart,
    stop: onDragStop
  }).each(function() {
    this.style.left = x;
    this.style.top = y;
    x += 90;
    if (x >= 300) {
      y += 90;
      x = 10;
    }
  });
}

function removeHashEntryByValue(h, v){
  for (var k in h) {
    if (h[k] == v) {
      delete h[k];
    }
  }
}

function putThisIntoThat(person, target) {
  if (pairingState[target]) {
    return false;
  }

  removeHashEntryByValue(pairingState, person);
  pairingState[target] = person;

  var td_position = $('#' + target).position();
  person = $('#' + person).get(0);
  person.style.top = td_position.top + 10;
  person.style.left = td_position.left + 15;

  return true;
}

function doUnpair(elem) {
  var id = elem.id;
  removeHashEntryByValue(pairingState, id);
  socket.emit('unpair', {
      person: id,
      top: elem.style.top,
      left: elem.style.left
   });
}

function doMove(data) {
  person = $('#' + data.person).get(0);
  person.style.top = data.top;
  person.style.left = data.left;
}

var socket = io.connect('/');
socket.on('init', function(data) {
  console.log(JSON.stringify(data));
  initialPageRender(data.personData);
  $.each(data['notes'], function(target, note) {
    setNote(target, note);
  });
  $.each(data['state'], function(target, person) {
    putThisIntoThat(person, target);
  });
});
socket.on('reset', function() {
  resetData();
  resetPersonPositions();
});
socket.on('pair', function(data) {
  putThisIntoThat(data.person, data.target);
});
socket.on('unpair', function(data) {
  removeHashEntryByValue(pairingState, data.person);
  doMove(data);
});
socket.on('move', function(data) {
  doMove(data);
});
socket.on('note', function(data) {
  setNote(data.target, data.note);
});
<|endoftext|>