<commit_before>/*
  Author: Joe Choi <joechoi910@gmail.com>
*/
'use strict';
var _ = require('underscore'),
  request = require('request'),
  clientId,
  clientSecret,
  restEndpoint,
  lists,
  campaigns,
  accessToken,
  leadId;

/*
  init(options, callback);
  Initiates Marketo API interface.

  Example:
  var options = {
    'clientId': '',
    'clientSecret': '',
    'restEndpoint': '',
    'lists': {
      'listname_1': listid_1,
      'listname_2': listid_2,
      ...
    },
    'campaigns': {
      'campaignname_1': campaignid_1,
      'campaignname_2': campaignid_2,
      ...
    },
  };
  marketo.init(options, function(response){
    console.log(response);
  });

  Success:
  { 'success': true, 'message': 'Marketo API variable are loaded OK and authenticated.', 'token': 'aaaaaa-bbbbbb-cccccc' }

  Error:
  { 'success':false, 'error':'Error message.' }
*/
exports.init = function (options, callback) {
  if(options.debug) console.log(options);
  clientId = options.clientId;
  clientSecret = options.clientSecret;
  restEndpoint = options.restEndpoint;
  lists = options.lists;
  campaigns = options.campaigns;
  authenticate(processInit, options, callback);
};

/*
  syncLead(options, callback);
  Sync leads on Marketo lead database and associates the lead to Marketo list.

  Example:
  var options: {
    'process': 'add' || 'update' || 'remove',
    'list': 'listname from 'lists':{...} in init()',
    'input': {
      'email': 'user@email.com',
      'firstName': 'John',
      'lastName': 'Doe',
      'title': 'Techie',
      'phone': '1112223333',
      'company': 'John Doe Company',
      'custom_field_1': 'custom_value_1',
      'custom_field_2': 'custom_value_2',
      ...
    }
  }
  marketo.syncLead(options, function(response){
    console.log(response);
  });

  Success:
  { 'success':true, 'message':'Lead Id ___ is successfully added to Marketo.' }

  Error:
  { 'success':false, 'error':'Error message.' }
*/
exports.syncLead = function (options, callback) {
  if(options.debug) console.log(options);
  authenticate(processSyncLead, options, callback);
};

/*
  sendEmail(options, callback);
  Schedules Marketo to send an email campaign to selected user.

  Example:
  var options: {
    'email': 'user@email.com',
    'campaign': 'campaignname from 'campaigns':{...} in init()',
    'tokens': [
      {'name': '{{token1_name}}', 'value': 'some value'},
      {'name': '{{token2_name}}', 'value': 'some value'},
      ...
    ]}
  }
  marketo.sendEmail(options, function(response){
    console.log(response);
  });

  Success:
  { 'success':true, 'message':'"confirm" email is sent to user@email.com.' }

  Error:
  { 'success':false, 'error':'Error message.' }
*/
exports.sendEmail = function (options, callback) {
  if(options.debug) console.log(options);
  authenticate(processSendEmail, options, callback);
};

/*
  After Marketo API variables are loaded, verify authentication and return token if authenticated.
*/
var processInit = function (options, callback){
  callback({'success':true, 'message':'Marketo API variable are loaded OK and authenticated.', 'token':accessToken});
};

/*
  After authenticated with Marketo API, execute lead sync job using Marketo API.

  For add & update lead,
    1) add or update lead
    2) add lead to list
  For remove lead,
    1) get lead id
    2) remove lead
    3) remove lead from list
*/
var processSyncLead = function (options, callback){
  var listId = '';
  if(options.list && lists[options.list]) {
    listId = lists[options.list];
  }
  switch(options.process.toLowerCase()){
    case 'add':
    case 'update':
      // 1) add or update lead
      addOrUpdateLead(options, function(response){
        if(options.debug) console.log('addOrUpdateLead:' + response.success);
        if(response.success === true){

          // 2) associate lead to list
          var leadId = response.leadId;
          var message = (options.process.toLowerCase() === 'add') ? 'Lead Id ' + leadId + ' is successfully added to Marketo.' : 'Lead Id ' + leadId + ' is successfully updated in Marketo.';
          if(listId !== '') {
            addLeadToList(leadId, listId, function(response){
              if(options.debug) console.log('addLeadToList:' + response.success);
              if(response.success === true){
                callback({'success':true, 'message':message});
              }
              else{
                callback({'success':false, 'error':response.error});
              }
            });
          }
          else{
            callback({'success':true, 'message':message});
          }
        }
        else{
          callback({'success':false, 'error':response.error});
        }
      });
      break;

    case 'remove':
      // 1) get lead id
      getLeadId(options.email, function(response){
        if(options.debug) console.log('getLeadId:' + response.success);
        if(response.success === true){

          // 2) remove lead
          var leadId = response.leadId;
          removeLead(leadId, function(response){
            if(options.debug) console.log('removeLead:' + response.success);
            if(response.success === true){

              // 3) remove lead from list
              if(listId !== '') {
                removeLeadFromList(leadId, listId, function(response){
                  if(options.debug) console.log('removeLeadFromList:' + response.success);
                  if(response.success === true){
                    callback({'success':true, 'message':'Lead Id ' + leadId + ' is successfully removed from Marketo.'});
                  }
                  else{
                    callback({'success':false, 'error':response.error});
                  }
                });
              }
              else{
                callback({'success':true, 'message':'Lead Id ' + leadId + ' is successfully removed from Marketo.'});
              }
            }
            else{
              callback({'success':false, 'error':response.error});
            }
          });
        }
        else{
          callback({'success':false, 'error':response.error});
        }
      });
      break;
  }
};

/*
  After authenticated with Marketo API, execute schedule campaign API call.
  1) get lead id
  2) request campaign
*/
var processSendEmail = function (options, callback){
  var campaignId = campaigns[options.campaign];
  if (typeof campaignId === 'undefined'){
    callback({'success':false, 'error':'campaignId is not found.'});
    return;
  }
  // 1) get lead id
  getLeadId(options.email, function(response){
    if(options.debug) console.log('getLeadId:' + response.success);
    if(response.success === true){

      // 2) request campaign
      var leadId = response.leadId;
      requestCampaign(campaignId, leadId, options.tokens, function(response){
        if(options.debug) console.log('requestCampaign:' + response.success);
        if(response.success === true){
          callback({'success':true, 'message':'"' + options.campaign + '" email has been sent to ' + options.email + '.'});
        }
        else{
          callback({'success':false, 'error':response.error});
        }
      });
    }
    else{
      callback({'success':false, 'error':response.error});
    }
  });
};

/*
  Authenticate to Marketo and store access token.
  Access token will expire every 1 hour by default.
  http://developers.marketo.com/documentation/rest/authentication/
*/
var authenticate = function (authCallback, options, callback) {
  var url = restEndpoint + 'identity/oauth/token?grant_type=client_credentials' + "&client_id=" + clientId + "&client_secret=" + clientSecret;
  request({
      method: 'GET',
      url: url,
      json: true
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body) && body.access_token) {
        accessToken = body.access_token;
        authCallback(options, callback);
      }
      else{
        callback({'success':false, 'error':'Marketo authentication failed.'});
      }
  });
};

/*
  Returns request header for Marketo API.
*/
var header = function () {
  var header = {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer ' + accessToken,
  };
  return header;
}

/*
  Returns lead id by matching email.
  http://developers.marketo.com/documentation/rest/get-multiple-leads-by-filter-type/
*/
var getLeadId = function (email, callback) {
  var url = restEndpoint + 'rest/v1/leads.json?access_token=' + accessToken + '&filterType=email&filterValues=' + _.escape(email);
  request({
      method: 'GET',
      headers: header(),
      url: url,
      json: true,
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body.result) && body.success === true) {
        callback({'success':true, 'leadId':body.result[0].id});
      }
      else{
        callback({'success':false, 'error':'Marketo Get Lead Id by Email API failed.'});
      }
  });
}

/*
  Adds Marketo lead to Marketo list.
  http://developers.marketo.com/documentation/rest/add-leads-to-list/
*/
var addLeadToList = function (leadId, listId, callback) {
  var url = restEndpoint + 'rest/v1/lists/' + listId + '/leads.json?access_token=' + accessToken;
  var data = {
    'input': [{
      'id': leadId
    }]
  };
  request({
      method: 'POST',
      headers: header(),
      url: url,
      body: data,
      json: true,
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body) && body.success === true && body.result[0].id) {
        callback({'success':true});
      }
      else{
        callback({'success':false, 'error':'Marketo Add Lead to List API failed.'});
      }
  });
};

/*
  Removes Marketo lead from Marketo list.
  http://developers.marketo.com/documentation/rest/remove-leads-from-list/
*/
var removeLeadFromList = function (leadId, listId, callback) {
  var url = restEndpoint + 'rest/v1/lists/' + listId + '/leads.json?access_token=' + accessToken + '&_method=DELETE';
  var data = {
    'input': [{
      'id': leadId
    }]
  };
  request({
      method: 'POST',
      headers: header(),
      url: url,
      body: data,
      json: true,
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body) && body.success === true && body.result[0].id) {
        callback({'success':true});
      }
      else{
        callback({'success':false, 'error':'Marketo Remove Lead from List API failed.'});
      }
  });
};

/*
  Adds or updates lead from Marketo.
  http://developers.marketo.com/documentation/rest/createupdate-leads/
*/
var addOrUpdateLead = function (options, callback){
  var url = restEndpoint + 'rest/v1/leads.json?access_token=' + accessToken;
  var data = {
    'action': 'createOrUpdate',
    'lookupField': 'email',
    'input': [options.input]
  };
  data.input[0].email = options.email;
  request({
      method: 'POST',
      headers: header(),
      url: url,
      body: data,
      json: true,
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body) && body.success === true && body.result[0].id) {
        callback({'success':true, 'leadId':body.result[0].id});
      }
      else{
        callback({'success':false, 'error':'Marketo Add & Edit Lead API failed.'});
      }
  });
};

/*
  Removes lead from Marketo.
  http://developers.marketo.com/documentation/rest/delete-lead/
*/
var removeLead = function (leadId, callback){
  var url = restEndpoint + 'rest/v1/leads.json?access_token=' + accessToken;
  var data = {
    'input': [{
      'id': leadId
    }]
  };
  request({
      method: 'DELETE',
      headers: header(),
      url: url,
      body: data,
      json: true,
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body.result) && body.success === true && body.result[0].id) {
        callback({'success':true});
      }
      else{
        callback({'success':false, 'error':'Marketo Remove Lead API failed.'});
      }
  });
};

/*
  Requests campaign in Marketo.
  http://developers.marketo.com/documentation/rest/request-campaign/
*/
var requestCampaign = function (campaignId, leadId, tokens, callback){
  var url = restEndpoint + 'rest/v1/campaigns/' + campaignId + '/trigger.json?access_token=' + accessToken;
  var data = {
    'input': {
      'leads': [{
        'id': leadId
      }],
    }
  };
  // add tokens if exists
  if (tokens && tokens.length > 0){
    data.input.tokens = tokens;
  }
  request({
      method: 'POST',
      headers: header(),
      url: url,
      body: data,
      json: true,
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body.result) && body.success === true && body.result[0].id) {
        callback({'success':true, 'campaignId':body.result[0].id});
      }
      else{
        callback({'success':false, 'error':'Marketo Request Campaign API failed.'});
      }
  });
};

<commit_msg>add associateLead and standardize callback responses<commit_after>/*
  Author: Joe Choi <joechoi910@gmail.com>
*/
'use strict';
var _ = require('underscore'),
  request = require('request'),
  clientId,
  clientSecret,
  restEndpoint,
  lists,
  campaigns,
  accessToken,
  leadId;

/*
  init(options, callback);
  Initiates Marketo API interface.

  Example:
  var options = {
    'clientId': '',
    'clientSecret': '',
    'restEndpoint': '',
    'lists': {
      'listname_1': listid_1,
      'listname_2': listid_2,
      ...
    },
    'campaigns': {
      'campaignname_1': campaignid_1,
      'campaignname_2': campaignid_2,
      ...
    },
  };
  marketo.init(options, function(response){
    console.log(response);
  });

  Success:
  { 'success': true, 'message': 'Marketo API variable are loaded OK and authenticated.', 'token': 'aaaaaa-bbbbbb-cccccc' }

  Error:
  { 'success':false, 'error':'Error message.' }
*/
exports.init = function (options, callback) {
  if(options.debug) console.log(options);
  clientId = options.clientId;
  clientSecret = options.clientSecret;
  restEndpoint = options.restEndpoint;
  lists = options.lists;
  campaigns = options.campaigns;
  authenticate(processInit, options, callback);
};

/*
  syncLead(options, callback);
  Sync leads on Marketo lead database and associates the lead to Marketo list.

  Example:
  var options: {
    'process': 'add' || 'update' || 'remove',
    'list': 'listname from 'lists':{...} in init()',
    'input': {
      'email': 'user@email.com',
      'firstName': 'John',
      'lastName': 'Doe',
      'title': 'Techie',
      'phone': '1112223333',
      'company': 'John Doe Company',
      'custom_field_1': 'custom_value_1',
      'custom_field_2': 'custom_value_2',
      ...
    }
  }
  marketo.syncLead(options, function(response){
    console.log(response);
  });

  Success:
  { 'success':true, 'message':'Lead Id ___ is successfully added to Marketo.' }

  Error:
  { 'success':false, 'error':'Error message.' }
*/
exports.syncLead = function (options, callback) {
  if(options.debug) console.log(options);
  authenticate(processSyncLead, options, callback);
};

/*
  sendEmail(options, callback);
  Schedules Marketo to send an email campaign to selected user.

  Example:
  var options: {
    'email': 'user@email.com',
    'campaign': 'campaignname from 'campaigns':{...} in init()',
    'tokens': [
      {'name': '{{token1_name}}', 'value': 'some value'},
      {'name': '{{token2_name}}', 'value': 'some value'},
      ...
    ]}
  }
  marketo.sendEmail(options, function(response){
    console.log(response);
  });

  Success:
  { 'success':true, 'message':'"confirm" email is sent to user@email.com.' }

  Error:
  { 'success':false, 'error':'Error message.' }
*/
exports.sendEmail = function (options, callback) {
  if(options.debug) console.log(options);
  authenticate(processSendEmail, options, callback);
};

/*
  After Marketo API variables are loaded, verify authentication and return token if authenticated.
*/
var processInit = function (options, callback){
  callback({'success':true, 'message':'Marketo API variable are loaded OK and authenticated.', 'token':accessToken});
};

/*
  After authenticated with Marketo API, execute lead sync job using Marketo API.

  For add & update lead,
    1) add or update lead
    2) add lead to list
  For remove lead,
    1) get lead id
    2) remove lead
    3) remove lead from list
*/
var processSyncLead = function (options, callback){
  var listId = '';
  if(options.list && lists[options.list]) {
    listId = lists[options.list];
  }
  switch(options.process.toLowerCase()){
    case 'add':
    case 'update':
      // 1) add or update lead
      addOrUpdateLead(options, function(response){
        if(options.debug) console.log('addOrUpdateLead:' + response.success);
        if(response.success === true){

          // 2) associate lead to list
          var leadId = response.leadId;
          var message = (options.process.toLowerCase() === 'add') ? 'Lead Id ' + leadId + ' is successfully added to Marketo.' : 'Lead Id ' + leadId + ' is successfully updated in Marketo.';
          if(listId !== '') {
            addLeadToList(leadId, listId, function(response){
              if(options.debug) console.log('addLeadToList:' + response.success);
              if(response.success === true){
                callback.apply(null, arguments);
              }
              else{
                callback.apply(null, arguments);
              }
            });
          }
          else{
            callback.apply(null, arguments);
          }
        }
        else{
          callback.apply(null, arguments);
        }
      });
      break;

    case 'remove':
      // 1) get lead id
      getLeadId(options.email, function(response){
        if(options.debug) console.log('getLeadId:' + response.success);
        if(response.success === true){

          // 2) remove lead
          var leadId = response.leadId;
          removeLead(leadId, function(response){
            if(options.debug) console.log('removeLead:' + response.success);
            if(response.success === true){

              // 3) remove lead from list
              if(listId !== '') {
                removeLeadFromList(leadId, listId, function(response){
                  if(options.debug) console.log('removeLeadFromList:' + response.success);
                  if(response.success === true){
                    response.message='Lead Id ' + leadId + ' is successfully removed from Marketo.';
                    callback(response);
                  } else{
                    response.success=false;
                    callback(response);
                  }
                });
              } else{
                response.message='Lead Id ' + leadId + ' is successfully removed from Marketo.';
                callback(response);
              }
            } else{
              response.success=false;
              callback(response);
            }
          });
        } else{
          response.success=false;
          callback(response);
        }
      });
      break;
  }
};

/*
  After authenticated with Marketo API, execute schedule campaign API call.
  1) get lead id
  2) request campaign
*/
var processSendEmail = function (options, callback){
  var campaignId = campaigns[options.campaign];
  if (typeof campaignId === 'undefined'){
    callback({'success':false, 'error':'campaignId is not found.'});
    return;
  }
  // 1) get lead id
  getLeadId(options.email, function(response){
    if(options.debug) console.log('getLeadId:' + response.success);
    if(response.success === true){

      // 2) request campaign
      var leadId = response.leadId;
      requestCampaign(campaignId, leadId, options.tokens, function(response){
        if(options.debug) console.log('requestCampaign:' + response.success);
        if(response.success === true){
          callback({'success':true, 'message':'"' + options.campaign + '" email has been sent to ' + options.email + '.'});
        }
        else{
          callback({'success':false, 'error':response.error});
        }
      });
    }
    else{
      callback({'success':false, 'error':response.error});
    }
  });
};

/*
  Authenticate to Marketo and store access token.
  Access token will expire every 1 hour by default.
  http://developers.marketo.com/documentation/rest/authentication/
*/
var authenticate = function (authCallback, options, callback) {
  var url = restEndpoint + 'identity/oauth/token?grant_type=client_credentials' + "&client_id=" + clientId + "&client_secret=" + clientSecret;
  request({
      method: 'GET',
      url: url,
      json: true
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body) && body.access_token) {
        accessToken = body.access_token;
        authCallback(options, callback);
      }
      else{
        response.success = false;
        response.error = error;
        response.message = 'Marketo authentication failed.';
        callback(response);
      }
  });
};

/*
  Returns request header for Marketo API.
*/
var header = function () {
  var header = {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer ' + accessToken,
  };
  return header;
}

/*
  Returns lead id by matching email.
  http://developers.marketo.com/documentation/rest/get-multiple-leads-by-filter-type/
*/
var getLeadId = function (email, callback) {
  var url = restEndpoint + 'rest/v1/leads.json?access_token=' + accessToken + '&filterType=email&filterValues=' + _.escape(email);
  request({
      method: 'GET',
      headers: header(),
      url: url,
      json: true,
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body.result) && body.success === true) {
        response.success = true;
        response.leadId = body.result[0].id;
        callback(response);
      }
      else{
        response.success = false;
        response.error = error;
        response.message = 'Marketo Get Lead Id by Email API failed.';
        callback(response);
      }
  });
}

/*
  Adds Marketo lead to Marketo list.
  http://developers.marketo.com/documentation/rest/add-leads-to-list/
*/
var addLeadToList = function (leadId, listId, callback) {
  var url = restEndpoint + 'rest/v1/lists/' + listId + '/leads.json?access_token=' + accessToken;
  var data = {
    'input': [{
      'id': leadId
    }]
  };
  request({
      method: 'POST',
      headers: header(),
      url: url,
      body: data,
      json: true,
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body) && body.success === true && body.result[0].id) {
        response.success = true
        callback(response);
      }
      else{
        response.success = false;
        response.error = error;
        response.message = 'Marketo Add Lead to List API failed.';
        callback(response);
      }
  });
};

/*
  Removes Marketo lead from Marketo list.
  http://developers.marketo.com/documentation/rest/remove-leads-from-list/
*/
var removeLeadFromList = function (leadId, listId, callback) {
  var url = restEndpoint + 'rest/v1/lists/' + listId + '/leads.json?access_token=' + accessToken + '&_method=DELETE';
  var data = {
    'input': [{
      'id': leadId
    }]
  };
  request({
      method: 'POST',
      headers: header(),
      url: url,
      body: data,
      json: true,
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body) && body.success === true && body.result[0].id) {
        response.success = true;
        callback(response);
      }
      else{
        response.success = false;
        response.error = error;
        response.message = 'Marketo Remove Lead from List API failed.'
        callback(response);
      }
  });
};

/*
  Adds or updates lead from Marketo.
  http://developers.marketo.com/documentation/rest/createupdate-leads/
*/
var addOrUpdateLead = function (options, callback){
  var url = restEndpoint + 'rest/v1/leads.json?access_token=' + accessToken;
  var data = {
    'action': 'createOrUpdate',
    'lookupField': 'email',
    'input': [options.input]
  };
  data.input[0].email = options.email;
  request({
      method: 'POST',
      headers: header(),
      url: url,
      body: data,
      json: true,
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body) && body.success === true && body.result[0].id) {
        response.success = true;
        response.leadId = body.result[0].id;
        callback(response);
      }
      else{
        response.success = false;
        response.error = error;
        response.message = 'Marketo Add & Edit Lead API failed.';
        callback(response);
      }
  });
};

/*
  Removes lead from Marketo.
  http://developers.marketo.com/documentation/rest/delete-lead/
*/
var removeLead = function (leadId, callback){
  var url = restEndpoint + 'rest/v1/leads.json?access_token=' + accessToken;
  var data = {
    'input': [{
      'id': leadId
    }]
  };
  request({
      method: 'DELETE',
      headers: header(),
      url: url,
      body: data,
      json: true,
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body.result) && body.success === true && body.result[0].id) {
        response.success = true;
        callback(response);
      }
      else{
        response.success = false;
        response.error = error;
        response.message = 'Marketo Remove Lead API failed.';
        callback(response);
      }
  });
};

/*
  Associates lead with marketo cookie
  http://developers.marketo.com/documentation/rest/associate-lead/
*/
var associateLeadWithCookie = function (leadId, cookieId, callback) {
  var token = '?access_token=' + accessToken;
  var cookie = '&cookie=' + cookieId.replace('&', '%46');
  var url = restEndpoint + 'rest/v1/leads/' + leadId + '/associate.json' + token + cookie;

  request({
    'method': 'POST',
    'headers': header(),
    'url': url,
    'data': {}
  }, function (error, response, body) {
    if (!error && response.statusCode === 200 && !_.isEmpty(body.result) && body.success === true && body.result[0].id) {
        response.success = true;
        callback(response);
      }
      else{
        response.success = false;
        response.error = error;
        response.message = 'Marketo Associate Lead API failed.';
        callback(response);
      }
  });
}

/*
  Requests campaign in Marketo.
  http://developers.marketo.com/documentation/rest/request-campaign/
*/
var requestCampaign = function (campaignId, leadId, tokens, callback){
  var url = restEndpoint + 'rest/v1/campaigns/' + campaignId + '/trigger.json?access_token=' + accessToken;
  var data = {
    'input': {
      'leads': [{
        'id': leadId
      }],
    }
  };
  // add tokens if exists
  if (tokens && tokens.length > 0){
    data.input.tokens = tokens;
  }
  request({
      method: 'POST',
      headers: header(),
      url: url,
      body: data,
      json: true,
  }, function (error, response, body) {
      if (!error && response.statusCode === 200 && !_.isEmpty(body.result) && body.success === true && body.result[0].id) {
        response.success = true;
        response.campaignId = body.result[0].id;
        callback({'success':true, 'campaignId':response});
      }
      else{
        response.success = false;
        response.error = error;
        response.message = 'Marketo Request Campaign API failed.';
        callback(response);
      }
  });
};

<|endoftext|>


<commit_before>(function (root, factory) {
	if ( typeof define === 'function' && define.amd ) {
		define('smoothScroll', factory(root));
	} else if ( typeof exports === 'object' ) {
		module.exports = factory(root);
	} else {
		root.smoothScroll = factory(root);
	}
})(this, function (root) {

	'use strict';

	//
	// Variables
	//

	var smoothScroll = {}; // Object for public APIs
	var supports = !!document.querySelector && !!root.addEventListener; // Feature test
	var settings;

	// Default settings
	var defaults = {
		speed: 500,
		easing: 'easeInOutCubic',
		offset: 0,
		updateURL: true,
		callbackBefore: function () {},
		callbackAfter: function () {}
	};

	//
	// Methods
	//

	/**
	 * A simple forEach() implementation for Arrays, Objects and NodeLists
	 * @private
	 * @param {Array|Object|NodeList} collection Collection of items to iterate
	 * @param {Function} callback Callback function for each iteration
	 * @param {Array|Object|NodeList} scope Object/NodeList/Array that forEach is iterating over (aka `this`)
	 */
	var forEach = function (collection, callback, scope) {
		if (Object.prototype.toString.call(collection) === '[object Object]') {
			for (var prop in collection) {
				if (Object.prototype.hasOwnProperty.call(collection, prop)) {
					callback.call(scope, collection[prop], prop, collection);
				}
			}
		} else {
			for (var i = 0, len = collection.length; i < len; i++) {
				callback.call(scope, collection[i], i, collection);
			}
		}
	};

	/**
	 * Merge defaults with user options
	 * @private
	 * @param {Object} defaults Default settings
	 * @param {Object} options User options
	 * @returns {Object} Merged values of defaults and options
	 */
	var extend = function ( defaults, options ) {
		var extended = {};
		forEach(defaults, function (value, prop) {
			extended[prop] = defaults[prop];
		});
		forEach(options, function (value, prop) {
			extended[prop] = options[prop];
		});
		return extended;
	};

	/**
	 * Get the closest matching element up the DOM tree
	 * @param {Element} elem Starting element
	 * @param {String} selector Selector to match against (class, ID, or data attribute)
	 * @return {Boolean|Element} Returns false if not match found
	 */
	var getClosest = function (elem, selector) {
		var firstChar = selector.charAt(0);
		for ( ; elem && elem !== document; elem = elem.parentNode ) {
			if ( firstChar === '.' ) {
				if ( elem.classList.contains( selector.substr(1) ) ) {
					return elem;
				}
			} else if ( firstChar === '#' ) {
				if ( elem.id === selector.substr(1) ) {
					return elem;
				}
			} else if ( firstChar === '[' ) {
				if ( elem.hasAttribute( selector.substr(1, selector.length - 2) ) ) {
					return elem;
				}
			}
		}
		return false;
	};

	/**
	 * Escape special characters for use with querySelector
	 * @private
	 * @param {String} id The anchor ID to escape
	 * @author Mathias Bynens
	 * @link https://github.com/mathiasbynens/CSS.escape
	 */
	var escapeCharacters = function ( id ) {
		var string = String(id);
		var length = string.length;
		var index = -1;
		var codeUnit;
		var result = '';
		var firstCodeUnit = string.charCodeAt(0);
		while (++index < length) {
			codeUnit = string.charCodeAt(index);
			// Note: there’s no need to special-case astral symbols, surrogate
			// pairs, or lone surrogates.

			// If the character is NULL (U+0000), then throw an
			// `InvalidCharacterError` exception and terminate these steps.
			if (codeUnit === 0x0000) {
				throw new InvalidCharacterError(
					'Invalid character: the input contains U+0000.'
				);
			}

			if (
				// If the character is in the range [\1-\1F] (U+0001 to U+001F) or is
				// U+007F, […]
				(codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F ||
				// If the character is the first character and is in the range [0-9]
				// (U+0030 to U+0039), […]
				(index === 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
				// If the character is the second character and is in the range [0-9]
				// (U+0030 to U+0039) and the first character is a `-` (U+002D), […]
				(
					index === 1 &&
					codeUnit >= 0x0030 && codeUnit <= 0x0039 &&
					firstCodeUnit === 0x002D
				)
			) {
				// http://dev.w3.org/csswg/cssom/#escape-a-character-as-code-point
				result += '\\' + codeUnit.toString(16) + ' ';
				continue;
			}

			// If the character is not handled by one of the above rules and is
			// greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or
			// is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to
			// U+005A), or [a-z] (U+0061 to U+007A), […]
			if (
				codeUnit >= 0x0080 ||
				codeUnit === 0x002D ||
				codeUnit === 0x005F ||
				codeUnit >= 0x0030 && codeUnit <= 0x0039 ||
				codeUnit >= 0x0041 && codeUnit <= 0x005A ||
				codeUnit >= 0x0061 && codeUnit <= 0x007A
			) {
				// the character itself
				result += string.charAt(index);
				continue;
			}

			// Otherwise, the escaped character.
			// http://dev.w3.org/csswg/cssom/#escape-a-character
			result += '\\' + string.charAt(index);

		}
		return result;
	};

	/**
	 * Calculate the easing pattern
	 * @private
	 * @param {String} type Easing pattern
	 * @param {Number} time Time animation should take to complete
	 * @returns {Number}
	 */
	var easingPattern = function ( type, time ) {
		var pattern;
		if ( type === 'easeInQuad' ) pattern = time * time; // accelerating from zero velocity
		if ( type === 'easeOutQuad' ) pattern = time * (2 - time); // decelerating to zero velocity
		if ( type === 'easeInOutQuad' ) pattern = time < 0.5 ? 2 * time * time : -1 + (4 - 2 * time) * time; // acceleration until halfway, then deceleration
		if ( type === 'easeInCubic' ) pattern = time * time * time; // accelerating from zero velocity
		if ( type === 'easeOutCubic' ) pattern = (--time) * time * time + 1; // decelerating to zero velocity
		if ( type === 'easeInOutCubic' ) pattern = time < 0.5 ? 4 * time * time * time : (time - 1) * (2 * time - 2) * (2 * time - 2) + 1; // acceleration until halfway, then deceleration
		if ( type === 'easeInQuart' ) pattern = time * time * time * time; // accelerating from zero velocity
		if ( type === 'easeOutQuart' ) pattern = 1 - (--time) * time * time * time; // decelerating to zero velocity
		if ( type === 'easeInOutQuart' ) pattern = time < 0.5 ? 8 * time * time * time * time : 1 - 8 * (--time) * time * time * time; // acceleration until halfway, then deceleration
		if ( type === 'easeInQuint' ) pattern = time * time * time * time * time; // accelerating from zero velocity
		if ( type === 'easeOutQuint' ) pattern = 1 + (--time) * time * time * time * time; // decelerating to zero velocity
		if ( type === 'easeInOutQuint' ) pattern = time < 0.5 ? 16 * time * time * time * time * time : 1 + 16 * (--time) * time * time * time * time; // acceleration until halfway, then deceleration
		return pattern || time; // no easing, no acceleration
	};

	/**
	 * Calculate how far to scroll
	 * @private
	 * @param {Element} anchor The anchor element to scroll to
	 * @param {Number} headerHeight Height of a fixed header, if any
	 * @param {Number} offset Number of pixels by which to offset scroll
	 * @returns {Number}
	 */
	var getEndLocation = function ( anchor, headerHeight, offset ) {
		var location = 0;
		if (anchor.offsetParent) {
			do {
				location += anchor.offsetTop;
				anchor = anchor.offsetParent;
			} while (anchor);
		}
		location = location - headerHeight - offset;
		return location >= 0 ? location : 0;
	};

	/**
	 * Determine the document's height
	 * @private
	 * @returns {Number}
	 */
	var getDocumentHeight = function () {
		return Math.max(
			document.body.scrollHeight, document.documentElement.scrollHeight,
			document.body.offsetHeight, document.documentElement.offsetHeight,
			document.body.clientHeight, document.documentElement.clientHeight
		);
	};

	/**
	 * Convert data-options attribute into an object of key/value pairs
	 * @private
	 * @param {String} options Link-specific options as a data attribute string
	 * @returns {Object}
	 */
	var getDataOptions = function ( options ) {
		return !options || !(typeof JSON === 'object' && typeof JSON.parse === 'function') ? {} : JSON.parse( options );
	};

	/**
	 * Update the URL
	 * @private
	 * @param {Element} anchor The element to scroll to
	 * @param {Boolean} url Whether or not to update the URL history
	 */
	var updateUrl = function ( anchor, url ) {
		if ( history.pushState && (url || url === 'true') ) {
			history.pushState( {
				pos: anchor.id
			}, '', window.location.pathname + anchor );
		}
	};

	/**
	 * Start/stop the scrolling animation
	 * @public
	 * @param {Element} toggle The element that toggled the scroll event
	 * @param {Element} anchor The element to scroll to
	 * @param {Object} settings
	 * @param {Event} event
	 */
	smoothScroll.animateScroll = function ( toggle, anchor, options ) {

		// Options and overrides
		var settings = extend( settings || defaults, options || {} );  // Merge user options with defaults
		var overrides = getDataOptions( toggle ? toggle.getAttribute('data-options') : null );
		settings = extend( settings, overrides );
		anchor = '#' + escapeCharacters(anchor.substr(1)); // Escape special characters and leading numbers

		// Selectors and variables
		var fixedHeader = document.querySelector('[data-scroll-header]'); // Get the fixed header
		var headerHeight = fixedHeader === null ? 0 : (fixedHeader.offsetHeight + fixedHeader.offsetTop); // Get the height of a fixed header if one exists
		var startLocation = root.pageYOffset; // Current location on the page
		var endLocation = getEndLocation( document.querySelector(anchor), headerHeight, parseInt(settings.offset, 10) ); // Scroll to location
		var animationInterval; // interval timer
		var distance = endLocation - startLocation; // distance to travel
		var documentHeight = getDocumentHeight();
		var timeLapsed = 0;
		var percentage, position;

		// Update URL
		updateUrl(anchor, settings.updateURL);

		/**
		 * Stop the scroll animation when it reaches its target (or the bottom/top of page)
		 * @private
		 * @param {Number} position Current position on the page
		 * @param {Number} endLocation Scroll to location
		 * @param {Number} animationInterval How much to scroll on this loop
		 */
		var stopAnimateScroll = function (position, endLocation, animationInterval) {
			var currentLocation = root.pageYOffset;
			if ( position == endLocation || currentLocation == endLocation || ( (root.innerHeight + currentLocation) >= documentHeight ) ) {
				clearInterval(animationInterval);
				settings.callbackAfter( toggle, anchor ); // Run callbacks after animation complete
			}
		};

		/**
		 * Loop scrolling animation
		 * @private
		 */
		var loopAnimateScroll = function () {
			timeLapsed += 16;
			percentage = ( timeLapsed / parseInt(settings.speed, 10) );
			percentage = ( percentage > 1 ) ? 1 : percentage;
			position = startLocation + ( distance * easingPattern(settings.easing, percentage) );
			root.scrollTo( 0, Math.floor(position) );
			stopAnimateScroll(position, endLocation, animationInterval);
		};

		/**
		 * Set interval timer
		 * @private
		 */
		var startAnimateScroll = function () {
			settings.callbackBefore( toggle, anchor ); // Run callbacks before animating scroll
			animationInterval = setInterval(loopAnimateScroll, 16);
		};

		/**
		 * Reset position to fix weird iOS bug
		 * @link https://github.com/cferdinandi/smooth-scroll/issues/45
		 */
		if ( root.pageYOffset === 0 ) {
			root.scrollTo( 0, 0 );
		}

		// Start scrolling animation
		startAnimateScroll();

	};

	/**
	 * If smooth scroll element clicked, animate scroll
	 * @private
	 */
	var eventHandler = function (event) {
		var toggle = getClosest(event.target, '[data-scroll]');
		if ( toggle && toggle.tagName.toLowerCase() === 'a' ) {
			event.preventDefault(); // Prevent default click event
			smoothScroll.animateScroll( toggle, toggle.hash, settings, event ); // Animate scroll
		}
	};

	/**
	 * Destroy the current initialization.
	 * @public
	 */
	smoothScroll.destroy = function () {
		if ( !settings ) return;
		document.removeEventListener( 'click', eventHandler, false );
		settings = null;
	};

	/**
	 * Initialize Smooth Scroll
	 * @public
	 * @param {Object} options User settings
	 */
	smoothScroll.init = function ( options ) {

		// feature test
		if ( !supports ) return;

		// Destroy any existing initializations
		smoothScroll.destroy();

		// Selectors and variables
		settings = extend( defaults, options || {} ); // Merge user options with defaults

		// When a toggle is clicked, run the click handler
		document.addEventListener('click', eventHandler, false);

	};

	//
	// Public APIs
	//

	return smoothScroll;

});
<commit_msg>use 'this' as a root only if 'window' object isn't available<commit_after>(function (root, factory) {
	if ( typeof define === 'function' && define.amd ) {
		define('smoothScroll', factory(root));
	} else if ( typeof exports === 'object' ) {
		module.exports = factory(root);
	} else {
		root.smoothScroll = factory(root);
	}
})(window || this, function (root) {

	'use strict';

	//
	// Variables
	//

	var smoothScroll = {}; // Object for public APIs
	var supports = !!document.querySelector && !!root.addEventListener; // Feature test
	var settings;

	// Default settings
	var defaults = {
		speed: 500,
		easing: 'easeInOutCubic',
		offset: 0,
		updateURL: true,
		callbackBefore: function () {},
		callbackAfter: function () {}
	};

	//
	// Methods
	//

	/**
	 * A simple forEach() implementation for Arrays, Objects and NodeLists
	 * @private
	 * @param {Array|Object|NodeList} collection Collection of items to iterate
	 * @param {Function} callback Callback function for each iteration
	 * @param {Array|Object|NodeList} scope Object/NodeList/Array that forEach is iterating over (aka `this`)
	 */
	var forEach = function (collection, callback, scope) {
		if (Object.prototype.toString.call(collection) === '[object Object]') {
			for (var prop in collection) {
				if (Object.prototype.hasOwnProperty.call(collection, prop)) {
					callback.call(scope, collection[prop], prop, collection);
				}
			}
		} else {
			for (var i = 0, len = collection.length; i < len; i++) {
				callback.call(scope, collection[i], i, collection);
			}
		}
	};

	/**
	 * Merge defaults with user options
	 * @private
	 * @param {Object} defaults Default settings
	 * @param {Object} options User options
	 * @returns {Object} Merged values of defaults and options
	 */
	var extend = function ( defaults, options ) {
		var extended = {};
		forEach(defaults, function (value, prop) {
			extended[prop] = defaults[prop];
		});
		forEach(options, function (value, prop) {
			extended[prop] = options[prop];
		});
		return extended;
	};

	/**
	 * Get the closest matching element up the DOM tree
	 * @param {Element} elem Starting element
	 * @param {String} selector Selector to match against (class, ID, or data attribute)
	 * @return {Boolean|Element} Returns false if not match found
	 */
	var getClosest = function (elem, selector) {
		var firstChar = selector.charAt(0);
		for ( ; elem && elem !== document; elem = elem.parentNode ) {
			if ( firstChar === '.' ) {
				if ( elem.classList.contains( selector.substr(1) ) ) {
					return elem;
				}
			} else if ( firstChar === '#' ) {
				if ( elem.id === selector.substr(1) ) {
					return elem;
				}
			} else if ( firstChar === '[' ) {
				if ( elem.hasAttribute( selector.substr(1, selector.length - 2) ) ) {
					return elem;
				}
			}
		}
		return false;
	};

	/**
	 * Escape special characters for use with querySelector
	 * @private
	 * @param {String} id The anchor ID to escape
	 * @author Mathias Bynens
	 * @link https://github.com/mathiasbynens/CSS.escape
	 */
	var escapeCharacters = function ( id ) {
		var string = String(id);
		var length = string.length;
		var index = -1;
		var codeUnit;
		var result = '';
		var firstCodeUnit = string.charCodeAt(0);
		while (++index < length) {
			codeUnit = string.charCodeAt(index);
			// Note: there’s no need to special-case astral symbols, surrogate
			// pairs, or lone surrogates.

			// If the character is NULL (U+0000), then throw an
			// `InvalidCharacterError` exception and terminate these steps.
			if (codeUnit === 0x0000) {
				throw new InvalidCharacterError(
					'Invalid character: the input contains U+0000.'
				);
			}

			if (
				// If the character is in the range [\1-\1F] (U+0001 to U+001F) or is
				// U+007F, […]
				(codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F ||
				// If the character is the first character and is in the range [0-9]
				// (U+0030 to U+0039), […]
				(index === 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
				// If the character is the second character and is in the range [0-9]
				// (U+0030 to U+0039) and the first character is a `-` (U+002D), […]
				(
					index === 1 &&
					codeUnit >= 0x0030 && codeUnit <= 0x0039 &&
					firstCodeUnit === 0x002D
				)
			) {
				// http://dev.w3.org/csswg/cssom/#escape-a-character-as-code-point
				result += '\\' + codeUnit.toString(16) + ' ';
				continue;
			}

			// If the character is not handled by one of the above rules and is
			// greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or
			// is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to
			// U+005A), or [a-z] (U+0061 to U+007A), […]
			if (
				codeUnit >= 0x0080 ||
				codeUnit === 0x002D ||
				codeUnit === 0x005F ||
				codeUnit >= 0x0030 && codeUnit <= 0x0039 ||
				codeUnit >= 0x0041 && codeUnit <= 0x005A ||
				codeUnit >= 0x0061 && codeUnit <= 0x007A
			) {
				// the character itself
				result += string.charAt(index);
				continue;
			}

			// Otherwise, the escaped character.
			// http://dev.w3.org/csswg/cssom/#escape-a-character
			result += '\\' + string.charAt(index);

		}
		return result;
	};

	/**
	 * Calculate the easing pattern
	 * @private
	 * @param {String} type Easing pattern
	 * @param {Number} time Time animation should take to complete
	 * @returns {Number}
	 */
	var easingPattern = function ( type, time ) {
		var pattern;
		if ( type === 'easeInQuad' ) pattern = time * time; // accelerating from zero velocity
		if ( type === 'easeOutQuad' ) pattern = time * (2 - time); // decelerating to zero velocity
		if ( type === 'easeInOutQuad' ) pattern = time < 0.5 ? 2 * time * time : -1 + (4 - 2 * time) * time; // acceleration until halfway, then deceleration
		if ( type === 'easeInCubic' ) pattern = time * time * time; // accelerating from zero velocity
		if ( type === 'easeOutCubic' ) pattern = (--time) * time * time + 1; // decelerating to zero velocity
		if ( type === 'easeInOutCubic' ) pattern = time < 0.5 ? 4 * time * time * time : (time - 1) * (2 * time - 2) * (2 * time - 2) + 1; // acceleration until halfway, then deceleration
		if ( type === 'easeInQuart' ) pattern = time * time * time * time; // accelerating from zero velocity
		if ( type === 'easeOutQuart' ) pattern = 1 - (--time) * time * time * time; // decelerating to zero velocity
		if ( type === 'easeInOutQuart' ) pattern = time < 0.5 ? 8 * time * time * time * time : 1 - 8 * (--time) * time * time * time; // acceleration until halfway, then deceleration
		if ( type === 'easeInQuint' ) pattern = time * time * time * time * time; // accelerating from zero velocity
		if ( type === 'easeOutQuint' ) pattern = 1 + (--time) * time * time * time * time; // decelerating to zero velocity
		if ( type === 'easeInOutQuint' ) pattern = time < 0.5 ? 16 * time * time * time * time * time : 1 + 16 * (--time) * time * time * time * time; // acceleration until halfway, then deceleration
		return pattern || time; // no easing, no acceleration
	};

	/**
	 * Calculate how far to scroll
	 * @private
	 * @param {Element} anchor The anchor element to scroll to
	 * @param {Number} headerHeight Height of a fixed header, if any
	 * @param {Number} offset Number of pixels by which to offset scroll
	 * @returns {Number}
	 */
	var getEndLocation = function ( anchor, headerHeight, offset ) {
		var location = 0;
		if (anchor.offsetParent) {
			do {
				location += anchor.offsetTop;
				anchor = anchor.offsetParent;
			} while (anchor);
		}
		location = location - headerHeight - offset;
		return location >= 0 ? location : 0;
	};

	/**
	 * Determine the document's height
	 * @private
	 * @returns {Number}
	 */
	var getDocumentHeight = function () {
		return Math.max(
			document.body.scrollHeight, document.documentElement.scrollHeight,
			document.body.offsetHeight, document.documentElement.offsetHeight,
			document.body.clientHeight, document.documentElement.clientHeight
		);
	};

	/**
	 * Convert data-options attribute into an object of key/value pairs
	 * @private
	 * @param {String} options Link-specific options as a data attribute string
	 * @returns {Object}
	 */
	var getDataOptions = function ( options ) {
		return !options || !(typeof JSON === 'object' && typeof JSON.parse === 'function') ? {} : JSON.parse( options );
	};

	/**
	 * Update the URL
	 * @private
	 * @param {Element} anchor The element to scroll to
	 * @param {Boolean} url Whether or not to update the URL history
	 */
	var updateUrl = function ( anchor, url ) {
		if ( history.pushState && (url || url === 'true') ) {
			history.pushState( {
				pos: anchor.id
			}, '', window.location.pathname + anchor );
		}
	};

	/**
	 * Start/stop the scrolling animation
	 * @public
	 * @param {Element} toggle The element that toggled the scroll event
	 * @param {Element} anchor The element to scroll to
	 * @param {Object} settings
	 * @param {Event} event
	 */
	smoothScroll.animateScroll = function ( toggle, anchor, options ) {

		// Options and overrides
		var settings = extend( settings || defaults, options || {} );  // Merge user options with defaults
		var overrides = getDataOptions( toggle ? toggle.getAttribute('data-options') : null );
		settings = extend( settings, overrides );
		anchor = '#' + escapeCharacters(anchor.substr(1)); // Escape special characters and leading numbers

		// Selectors and variables
		var fixedHeader = document.querySelector('[data-scroll-header]'); // Get the fixed header
		var headerHeight = fixedHeader === null ? 0 : (fixedHeader.offsetHeight + fixedHeader.offsetTop); // Get the height of a fixed header if one exists
		var startLocation = root.pageYOffset; // Current location on the page
		var endLocation = getEndLocation( document.querySelector(anchor), headerHeight, parseInt(settings.offset, 10) ); // Scroll to location
		var animationInterval; // interval timer
		var distance = endLocation - startLocation; // distance to travel
		var documentHeight = getDocumentHeight();
		var timeLapsed = 0;
		var percentage, position;

		// Update URL
		updateUrl(anchor, settings.updateURL);

		/**
		 * Stop the scroll animation when it reaches its target (or the bottom/top of page)
		 * @private
		 * @param {Number} position Current position on the page
		 * @param {Number} endLocation Scroll to location
		 * @param {Number} animationInterval How much to scroll on this loop
		 */
		var stopAnimateScroll = function (position, endLocation, animationInterval) {
			var currentLocation = root.pageYOffset;
			if ( position == endLocation || currentLocation == endLocation || ( (root.innerHeight + currentLocation) >= documentHeight ) ) {
				clearInterval(animationInterval);
				settings.callbackAfter( toggle, anchor ); // Run callbacks after animation complete
			}
		};

		/**
		 * Loop scrolling animation
		 * @private
		 */
		var loopAnimateScroll = function () {
			timeLapsed += 16;
			percentage = ( timeLapsed / parseInt(settings.speed, 10) );
			percentage = ( percentage > 1 ) ? 1 : percentage;
			position = startLocation + ( distance * easingPattern(settings.easing, percentage) );
			root.scrollTo( 0, Math.floor(position) );
			stopAnimateScroll(position, endLocation, animationInterval);
		};

		/**
		 * Set interval timer
		 * @private
		 */
		var startAnimateScroll = function () {
			settings.callbackBefore( toggle, anchor ); // Run callbacks before animating scroll
			animationInterval = setInterval(loopAnimateScroll, 16);
		};

		/**
		 * Reset position to fix weird iOS bug
		 * @link https://github.com/cferdinandi/smooth-scroll/issues/45
		 */
		if ( root.pageYOffset === 0 ) {
			root.scrollTo( 0, 0 );
		}

		// Start scrolling animation
		startAnimateScroll();

	};

	/**
	 * If smooth scroll element clicked, animate scroll
	 * @private
	 */
	var eventHandler = function (event) {
		var toggle = getClosest(event.target, '[data-scroll]');
		if ( toggle && toggle.tagName.toLowerCase() === 'a' ) {
			event.preventDefault(); // Prevent default click event
			smoothScroll.animateScroll( toggle, toggle.hash, settings, event ); // Animate scroll
		}
	};

	/**
	 * Destroy the current initialization.
	 * @public
	 */
	smoothScroll.destroy = function () {
		if ( !settings ) return;
		document.removeEventListener( 'click', eventHandler, false );
		settings = null;
	};

	/**
	 * Initialize Smooth Scroll
	 * @public
	 * @param {Object} options User settings
	 */
	smoothScroll.init = function ( options ) {

		// feature test
		if ( !supports ) return;

		// Destroy any existing initializations
		smoothScroll.destroy();

		// Selectors and variables
		settings = extend( defaults, options || {} ); // Merge user options with defaults

		// When a toggle is clicked, run the click handler
		document.addEventListener('click', eventHandler, false);

	};

	//
	// Public APIs
	//

	return smoothScroll;

});
<|endoftext|>


<commit_before>'use strict';

var Parth = require('parth');
var Runtime = require('runtime');
var vinylFS = require('vinyl-fs')

var util = require('./lib/util');
var __slice = Array.prototype.slice;
var fromGulpBin = /gulp$/.test(process.argv[1]);
var taskSimpleFlag = process.argv.indexOf('--tasks-simple') > -1;

var Gulp = module.exports = Runtime.createClass({
  src: vinylFS.src,
  dest: vinylFS.dest,
  create: function Gulp(props){
    Gulp.super_.call(this, props);
    this.tasks = new Parth();

    this.log = this.log === void 0 || this.log;
    if(this.repl && taskSimpleFlag){ this.repl = false; }
    this.gulpfile = util.getGulpFile();

    // when the gulpfile is passed as argument default to process.argv
    if(!this.argv && (process.argv[1] === this.gulpfile || fromGulpBin)){
      this.argv = process.argv.slice(2);
    } else {
      this.argv = util.type(this.argv).array;
    }

    if(this.repl){
      util.log('REPL', util.format.enabled('enabled'));
      this.repl = require('gulp-repl')(this);
    }

    if(this.log && !fromGulpBin){
      util.log('Using gulpfile ' + util.format.path(this.gulpfile));
    }
    // add cli tasks and run the cli for this instance
    require('./lib/cli')(this, this.argv);
  }
});

/**
 * gulp.task
**/

Gulp.prototype.task = function(name, deps, handle){
  handle = util.type(handle || deps || name).function;
  deps = util.type(deps).array || util.type(name).array;
  name = (util.type(name).string || util.type(handle.name).string || '').trim();

  if(name && !deps && !handle){
    return this.tasks.get(name).fn || null;
  }

  handle = handle || function(next){ next(); };
  if(name && handle && name !== handle.name){
    handle.displayName = name;
  }

  if(name && deps){
    deps = deps.concat(handle, {wait: true});
    var composed = this.stack.apply(this, deps);
    this.tasks.set(name, {label: name, fn: composed});
  } else if(name){
    this.tasks.set(name, {label: name, fn: handle});
  } else if(!name){
    throw new TypeError('no task name, give a named function');
  }

  return this;
};

/**
 * gulp.watch
 **/
Gulp.prototype.watch = function(glob, opt, handle) {
  var fn = util.type(handle || opt).function;
  var tasks = util.type(opt).array;

  if(tasks){
    fn = fn || function onStackEnd(){ }
    var composer = this.stack.apply(this, tasks);
    return vinylFS.watch(glob, function(/* arguments */){
      composer.apply(null, __slice.call(arguments).concat(fn));
    });
  }

  return vinylFS.watch(glob, opt, fn);
};

/**
 maps all the arguments of gulp.stack to functions
**/
Gulp.prototype.reduceStack = function(stack, site){
  var task = typeof site === 'function'
    ? {fn: site, label: site.displayName || site.name || 'anonymous'}
    : this.tasks.get(site);

  if(task){
    stack.push(task);
  } else if(typeof site === 'string'){

    util.log('Task `%s` is %s',
      util.format.task(site),
      util.format.error('not defined yet')
    );

    util.log('Not sure how to do that? See: %s',
      util.format.link(util.docs.task)
    );

    throw new Error('task not defined yet');
  }
};

/**
 logging
**/
Gulp.prototype.onHandle = function(task, stack){
  if(!this.log || (task.params && task.params.cli)){
    return;
  }

  var deep = stack.length > 1;

  if(deep && !stack.time){
    stack.time = process.hrtime();
    stack.mode = stack.wait ? 'series' : 'parallel';
    stack.tree = stack.tree();
    util.log('Started %s(%s)',
      util.format.mode(stack.mode),
      util.format.task(stack.tree.label)
    );
  } else if(!stack.time && !stack.end){
    util.log('Starting %s', util.format.task(task.label));
  }

  if(!task.time){
    task.time = process.hrtime();
    return;
  }

  if(!(task.fn.stack instanceof Runtime.Stack)){
    var time = util.format.time(task.time);
    util.log('%s %s %s',
      (deep ? ' ' : '') + util.format.task(task.label),
      deep ? 'took' : 'ended after',
      time
    );
  }

  if(deep && stack.time && stack.end){
    util.log('Ended %s(%s) after %s',
      util.format.mode(stack.mode),
      util.format.task(stack.tree.label),
      util.format.time(stack.time)
    );
  }

  if(!deep && stack.end && this.repl){
    this.repl.prompt();
  }
};

/**
 error handling
**/
Gulp.prototype.onHandleError = function(err, site, stack){
  util.log('%s in %s',
    util.format.error('error'),
    stack.tree ? stack.tree.label : stack.tree().label,
    this.repl ? '\n' + err.stack : ''
  );

  console.log('- %s failed after %s',
    util.format.error(site.label),
    util.format.time(site.time)
  );

  if(!this.repl){ throw err; }
};

/**
  With some sugar on top please
**/

Gulp.prototype.start = function(/* arguments */){
  this.stack.apply(this, arguments.length ? arguments : ['default'])();
  return this;
};

Gulp.prototype.series = function(/* arguments */){
  var args = __slice.call(arguments);
  var props = util.type(args[args.length - 1]).plainObject && args.pop() || {};
  var tasks = args.concat(util.merge(props, {wait: false}));
  return this.stack.apply(this, tasks);
};

Gulp.prototype.parallel = function(/* arguments */){
  var args = __slice.call(arguments);
  var props = util.type(args[args.length - 1]).plainObject && args.pop() || {};
  var tasks = args.concat(util.merge(props, {wait: false}));
  return this.stack.apply(this, tasks);
};
<commit_msg>[ci skip] move stack.tree to gulp-runtime<commit_after>'use strict';

var Parth = require('parth');
var Runtime = require('runtime');
var vinylFS = require('vinyl-fs')

var util = require('./lib/util');
var __slice = Array.prototype.slice;
var fromGulpBin = /gulp$/.test(process.argv[1]);
var taskSimpleFlag = process.argv.indexOf('--tasks-simple') > -1;

var Gulp = module.exports = Runtime.createClass({
  src: vinylFS.src,
  dest: vinylFS.dest,
  create: function Gulp(props){
    Gulp.super_.call(this, props);
    this.tasks = new Parth();

    this.log = this.log === void 0 || this.log;
    if(this.repl && taskSimpleFlag){ this.repl = false; }
    this.gulpfile = util.getGulpFile();

    // when the gulpfile is passed as argument default to process.argv
    if(!this.argv && (process.argv[1] === this.gulpfile || fromGulpBin)){
      this.argv = process.argv.slice(2);
    } else {
      this.argv = util.type(this.argv).array;
    }

    if(this.repl){
      util.log('REPL', util.format.enabled('enabled'));
      this.repl = require('gulp-repl')(this);
    }

    if(this.log && !fromGulpBin){
      util.log('Using gulpfile ' + util.format.path(this.gulpfile));
    }
    // add cli tasks and run the cli for this instance
    require('./lib/cli')(this, this.argv);
  }
});

/**
 * gulp.task
**/

Gulp.prototype.task = function(name, deps, handle){
  handle = util.type(handle || deps || name).function;
  deps = util.type(deps).array || util.type(name).array;
  name = (util.type(name).string || util.type(handle.name).string || '').trim();

  if(name && !deps && !handle){
    return this.tasks.get(name).fn || null;
  }

  handle = handle || function(next){ next(); };
  if(name && handle && name !== handle.name){
    handle.displayName = name;
  }

  if(name && deps){
    deps = deps.concat(handle, {wait: true});
    var composed = this.stack.apply(this, deps);
    this.tasks.set(name, {label: name, fn: composed});
  } else if(name){
    this.tasks.set(name, {label: name, fn: handle});
  } else if(!name){
    throw new TypeError('no task name, give a named function');
  }

  return this;
};

/**
 * gulp.watch
 **/
Gulp.prototype.watch = function(glob, opt, handle) {
  var fn = util.type(handle || opt).function;
  var tasks = util.type(opt).array;

  if(tasks){
    fn = fn || function onStackEnd(){ }
    var composer = this.stack.apply(this, tasks);
    return vinylFS.watch(glob, function(/* arguments */){
      composer.apply(null, __slice.call(arguments).concat(fn));
    });
  }

  return vinylFS.watch(glob, opt, fn);
};

/**

**/
Gulp.prototype.Stack.prototype.tree = function(options){
  options = options || {deep: true};
  options.depth = util.type(options.depth).number || 0;

  var tree = {label: '', nodes: []};
  var sites = [];

  sites = this.length ? this : this.reduce();
  for (var index = 0, length = sites.length; index < length; ++index) {
    var site = sites[index];
    if(!site || !site.fn){ continue; }

    if(options.deep && site.fn.stack instanceof Runtime.Stack){
      tree.label = tree.label || site.fn.displayName || '';
      tree.nodes.push(
        site.fn.stack.tree(
          util.merge({}, options, {host: site})
        )
      );
    } else {
      tree.nodes.push(site)
    }

    tree.label += (tree.label && ', ' + site.label) || site.label;
  }

  if(options.host && options.host.label){
    tree.label = options.host.label;
    tree.nodes = tree.nodes.filter(function(node){
      return node.label !== options.host.label;
    });
  }

  return tree;
};

/**
 maps all the arguments of gulp.stack to functions
**/
Gulp.prototype.reduceStack = function(stack, site){
  var task = this.tasks.get(site);

  if(!task && typeof site === 'function'){
    task = {
      fn: site,
      label: site.stack instanceof Runtime.Stack
        ? site.stack.tree().label
        : site.displayName || site.name || 'anonymous'
    };
  }

  if(task){
    stack.push(task);
  } else if(typeof site === 'string'){

    util.log('Task `%s` is %s',
      util.format.task(site),
      util.format.error('not defined yet')
    );

    util.log('Not sure how to do that? See: %s',
      util.format.link(util.docs.task)
    );

    throw new Error('task not defined yet');
  }
};

/**
 logging
**/
Gulp.prototype.onHandle = function(task, stack){
  if(!this.log || (task.params && task.params.cli)){
    return;
  }

  var deep = stack.length > 1;

  if(deep && !stack.time){
    stack.time = process.hrtime();
    stack.mode = stack.wait ? 'series' : 'parallel';
    stack.tree = stack.tree();
    util.log('Started %s(%s)',
      util.format.mode(stack.mode),
      util.format.task(stack.tree.label)
    );
  } else if(!stack.time && !stack.end){
    util.log('Starting %s', util.format.task(task.label));
  }

  if(!task.time){
    task.time = process.hrtime();
    return;
  }

  if(!(task.fn.stack instanceof Runtime.Stack)){
    var time = util.format.time(task.time);
    util.log('%s %s %s',
      (deep ? ' ' : '') + util.format.task(task.label),
      deep ? 'took' : 'ended after',
      time
    );
  }

  if(deep && stack.time && stack.end){
    util.log('Ended %s(%s) after %s',
      util.format.mode(stack.mode),
      util.format.task(stack.tree.label),
      util.format.time(stack.time)
    );
  }

  if(this.repl && !deep && stack.end){
    this.repl.prompt();
  }
};

/**
 error handling
**/
Gulp.prototype.onHandleError = function(err, site, stack){
  util.log('%s in %s',
    util.format.error('error'),
    stack.tree ? stack.tree.label : stack.tree().label,
    this.repl ? '\n' + err.stack : ''
  );

  console.log('- %s failed after %s',
    util.format.error(site.label),
    util.format.time(site.time)
  );

  if(!this.repl){ throw err; }
};

/**
  With some sugar on top please
**/

Gulp.prototype.start = function(/* arguments */){
  this.stack.apply(this, arguments.length ? arguments : ['default'])();
  return this;
};

Gulp.prototype.series = function(/* arguments */){
  var args = __slice.call(arguments);
  var props = util.type(args[args.length - 1]).plainObject && args.pop() || {};
  var tasks = args.concat(util.merge(props, {wait: false}));
  return this.stack.apply(this, tasks);
};

Gulp.prototype.parallel = function(/* arguments */){
  var args = __slice.call(arguments);
  var props = util.type(args[args.length - 1]).plainObject && args.pop() || {};
  var tasks = args.concat(util.merge(props, {wait: false}));
  return this.stack.apply(this, tasks);
};
<|endoftext|>


<commit_before>'use strict';

var argv = require('yargs').argv;
var conf = require('confucious');
var path = require('path');
var fs = require('fs-extra');
var globby = require('globby');
var chalk = require('chalk');
var validate = require('./validate');
var S = require('string');
var AsciiTable = require('ascii-table');
var assert = require('chai').assert;
var loadTasks = require('./task-loader')
var JobRunner = require('./job-runner');

var workingDirectory = process.cwd();
var buildFilePath = path.join(workingDirectory, "mule.js");
var tasksDirectory = path.join(workingDirectory, 'tasks');

//
// task-mule init
//
var commandInit = function (config, log) {

	if (fs.existsSync(buildFilePath)) {
		log.error("Can't overwrite existing 'mule.js'.");
		process.exit(1);
	}

	// Auto create mule.js.
	var defaultBuildJs = path.join(__dirname, 'template', 'mule.js');
	fs.copySync(defaultBuildJs, buildFilePath);
	log.info("Created new 'mule.js' at " + buildFilePath);
	process.exit(0);
};

//
// task-mule create-task <task-name>
//
var commandCreateTask = function (config, log) {

	var newTaskName = argv._[1];
	if (!newTaskName) {
		log.error("Task name not specified.");
		process.exit(1);
	}

	if (!S(newTaskName.toLowerCase()).endsWith(".js")) {
		if (newTaskName[newTaskName.length-1] === '.') {
			// Trim final period.
			newTaskName = newTaskName.substring(0, newTaskName.length-1);
		}

		// Auto add extension.
		newTaskName += ".js";
	}

	var newTaskFilePath = path.join(tasksDirectory, newTaskName);
	if (fs.existsSync(newTaskFilePath)) {
		log.error("Can't create task, file already exists: " + newTaskFilePath);
		process.exit(1);
	}

	var defaultTaskFile = path.join(__dirname, 'template', 'task.js');
	fs.copySync(defaultTaskFile, newTaskFilePath);
	log.info("Created new task file at " + newTaskFilePath);
};

//
// Init config prior to running or listing tasks.
//
var initConfig = function (config, log) {

	var buildConfig = require(buildFilePath)(conf, log, validate);

	var defaultConfigFilePath = path.join(workingDirectory, 'config.json');
	if (fs.existsSync(defaultConfigFilePath)) {

		log.verbose("Loading config from file: " + defaultConfigFilePath);

		conf.pushJsonFile(defaultConfigFilePath);
	}

	conf.pushEnv();

	if (buildConfig.initConfig) {
		buildConfig.initConfig();
	}

	conf.pushArgv();

	if (config.defaultConfig) {
		conf.push(config.defaultConfig)
	}

	buildConfig.init();

	return buildConfig;
};

//
// task-mule schedule
//
var commandSchedule = function (config, log) {

	if (!fs.existsSync('schedule.json')) {
		log.error('Expected schedule.json to specify the schedule of tasks.');
		process.exit(1);
	}

	var buildConfig = initConfig(config, log);

	var taskRunner = loadTasks({}, log, validate, conf);
	var jobRunner = new JobRunner(taskRunner, log, buildConfig);

	var schedule = JSON.parse(fs.readFileSync('schedule.json', 'utf8'));

	var TaskScheduler = require('./task-scheduler');
	var taskScheduler = new TaskScheduler(jobRunner, config, log);
	taskScheduler.start(schedule, buildConfig);
};

//
// task-mule <task-name>
//
var commandRunTask = function (config, log, requestedTaskName) {

	if (!fs.existsSync(buildFilePath)) {
		log.error("'mule.js' not found, please run task-mule in a directory that has this file.");
		log.info("Run 'task-mule init' to create a default 'mule.js'.")
		process.exit(1);
	}

	if (!fs.existsSync(tasksDirectory)) {
		log.error("'tasks' directory doesn't exist.");
		log.info("Run 'task-mule create-task <task-name> to create your first task.");
		process.exit(1);
	}

	var buildConfig = initConfig(config, log);

	var taskRunner = loadTasks({}, log, validate, conf);
	var jobRunner = new JobRunner(taskRunner, log, buildConfig);

	if (requestedTaskName) {
	    return jobRunner.runTask(requestedTaskName, conf, {})
            .catch(function (err) {

                log.error('Build failed.');

                if (err.message) {
                    log.warn(err.message);
                }

                if (err.stack) {
                    log.warn(err.stack);
                }
                else {
                    log.warn('no stack');
                }
                if (!config.noExit) {
                	process.exit(1);
                }

                buildConfig.done();

                throw err;
            })
	        .then(function () {
        		buildConfig.done();
	        });
	}
	else if (argv.tasks) {
	    return taskRunner.resolveAllDependencies(conf)
	    	.then(function () {
			    taskRunner.listTasks();
			    process.exit(1);
			});
	}
	else {
	    log.info("Usage: task-mule <task-name> [options]\n");

	    var optionsTable = new AsciiTable('Options');
	    optionsTable
	      .setHeading('Options', 'Description');

	    buildConfig.options.forEach(function (option) {
	        optionsTable.addRow(option[0], option[1]);
	    });

	    console.log(chalk.bold.green(optionsTable.toString()));

	    var examplesTable = new AsciiTable('Examples');
	    examplesTable.setHeading('What?', 'Command Line');

	     buildConfig.examples.forEach(function (example) {
	     	examplesTable.addRow(example[0], example[1]);
	     });

	    console.log(chalk.bold.green(examplesTable.toString()));

	    process.exit(1);
	}
};

module.exports = function (config) {

	config = config || {};

	var log = config.log || require('./log')(argv.verbose, argv.nocolors);

	global.runCmd = require('./run-cmd')(log);

	var requestedTaskName = config.requestedTaskName || argv._[0];
	if (requestedTaskName === 'init') {
		commandInit(config, log);
		process.exit(0);
	}
	else if (requestedTaskName === 'create-task') {
		commandCreateTask(config, log);
		process.exit(0);
	}
	else if (requestedTaskName === 'schedule') {
		commandSchedule(config, log);
		return;
	}

	return commandRunTask(config, log, requestedTaskName);
};

<commit_msg>Default config is now applied before user config and args.<commit_after>'use strict';

var argv = require('yargs').argv;
var conf = require('confucious');
var path = require('path');
var fs = require('fs-extra');
var globby = require('globby');
var chalk = require('chalk');
var validate = require('./validate');
var S = require('string');
var AsciiTable = require('ascii-table');
var assert = require('chai').assert;
var loadTasks = require('./task-loader')
var JobRunner = require('./job-runner');

var workingDirectory = process.cwd();
var buildFilePath = path.join(workingDirectory, "mule.js");
var tasksDirectory = path.join(workingDirectory, 'tasks');

//
// task-mule init
//
var commandInit = function (config, log) {

	if (fs.existsSync(buildFilePath)) {
		log.error("Can't overwrite existing 'mule.js'.");
		process.exit(1);
	}

	// Auto create mule.js.
	var defaultBuildJs = path.join(__dirname, 'template', 'mule.js');
	fs.copySync(defaultBuildJs, buildFilePath);
	log.info("Created new 'mule.js' at " + buildFilePath);
	process.exit(0);
};

//
// task-mule create-task <task-name>
//
var commandCreateTask = function (config, log) {

	var newTaskName = argv._[1];
	if (!newTaskName) {
		log.error("Task name not specified.");
		process.exit(1);
	}

	if (!S(newTaskName.toLowerCase()).endsWith(".js")) {
		if (newTaskName[newTaskName.length-1] === '.') {
			// Trim final period.
			newTaskName = newTaskName.substring(0, newTaskName.length-1);
		}

		// Auto add extension.
		newTaskName += ".js";
	}

	var newTaskFilePath = path.join(tasksDirectory, newTaskName);
	if (fs.existsSync(newTaskFilePath)) {
		log.error("Can't create task, file already exists: " + newTaskFilePath);
		process.exit(1);
	}

	var defaultTaskFile = path.join(__dirname, 'template', 'task.js');
	fs.copySync(defaultTaskFile, newTaskFilePath);
	log.info("Created new task file at " + newTaskFilePath);
};

//
// Init config prior to running or listing tasks.
//
var initConfig = function (config, log) {

	var buildConfig = require(buildFilePath)(conf, log, validate);

	var defaultConfigFilePath = path.join(workingDirectory, 'config.json');
	if (fs.existsSync(defaultConfigFilePath)) {

		log.verbose("Loading config from file: " + defaultConfigFilePath);

		conf.pushJsonFile(defaultConfigFilePath);
	}

	conf.pushEnv();

	if (config.defaultConfig) {
		conf.push(config.defaultConfig)
	}

	if (buildConfig.initConfig) {
		buildConfig.initConfig();
	}

	conf.pushArgv();

	buildConfig.init();

	return buildConfig;
};

//
// task-mule schedule
//
var commandSchedule = function (config, log) {

	if (!fs.existsSync('schedule.json')) {
		log.error('Expected schedule.json to specify the schedule of tasks.');
		process.exit(1);
	}

	var buildConfig = initConfig(config, log);

	var taskRunner = loadTasks({}, log, validate, conf);
	var jobRunner = new JobRunner(taskRunner, log, buildConfig);

	var schedule = JSON.parse(fs.readFileSync('schedule.json', 'utf8'));

	var TaskScheduler = require('./task-scheduler');
	var taskScheduler = new TaskScheduler(jobRunner, config, log);
	taskScheduler.start(schedule, buildConfig);
};

//
// task-mule <task-name>
//
var commandRunTask = function (config, log, requestedTaskName) {

	if (!fs.existsSync(buildFilePath)) {
		log.error("'mule.js' not found, please run task-mule in a directory that has this file.");
		log.info("Run 'task-mule init' to create a default 'mule.js'.")
		process.exit(1);
	}

	if (!fs.existsSync(tasksDirectory)) {
		log.error("'tasks' directory doesn't exist.");
		log.info("Run 'task-mule create-task <task-name> to create your first task.");
		process.exit(1);
	}

	var buildConfig = initConfig(config, log);

	var taskRunner = loadTasks({}, log, validate, conf);
	var jobRunner = new JobRunner(taskRunner, log, buildConfig);

	if (requestedTaskName) {
	    return jobRunner.runTask(requestedTaskName, conf, {})
            .catch(function (err) {

                log.error('Build failed.');

                if (err.message) {
                    log.warn(err.message);
                }

                if (err.stack) {
                    log.warn(err.stack);
                }
                else {
                    log.warn('no stack');
                }
                if (!config.noExit) {
                	process.exit(1);
                }

                buildConfig.done();

                throw err;
            })
	        .then(function () {
        		buildConfig.done();
	        });
	}
	else if (argv.tasks) {
	    return taskRunner.resolveAllDependencies(conf)
	    	.then(function () {
			    taskRunner.listTasks();
			    process.exit(1);
			});
	}
	else {
	    log.info("Usage: task-mule <task-name> [options]\n");

	    var optionsTable = new AsciiTable('Options');
	    optionsTable
	      .setHeading('Options', 'Description');

	    buildConfig.options.forEach(function (option) {
	        optionsTable.addRow(option[0], option[1]);
	    });

	    console.log(chalk.bold.green(optionsTable.toString()));

	    var examplesTable = new AsciiTable('Examples');
	    examplesTable.setHeading('What?', 'Command Line');

	     buildConfig.examples.forEach(function (example) {
	     	examplesTable.addRow(example[0], example[1]);
	     });

	    console.log(chalk.bold.green(examplesTable.toString()));

	    process.exit(1);
	}
};

module.exports = function (config) {

	config = config || {};

	var log = config.log || require('./log')(argv.verbose, argv.nocolors);

	global.runCmd = require('./run-cmd')(log);

	var requestedTaskName = config.requestedTaskName || argv._[0];
	if (requestedTaskName === 'init') {
		commandInit(config, log);
		process.exit(0);
	}
	else if (requestedTaskName === 'create-task') {
		commandCreateTask(config, log);
		process.exit(0);
	}
	else if (requestedTaskName === 'schedule') {
		commandSchedule(config, log);
		return;
	}

	return commandRunTask(config, log, requestedTaskName);
};

<|endoftext|>


<commit_before>import { attr } from './attr';
import { closest, index, matches, parent } from './filter';
import { isDocument, isString, memoize, toNode, toNodes } from './lang';

export function query(selector, context) {
    return find(selector, getContext(selector, context));
}

export function queryAll(selector, context) {
    return findAll(selector, getContext(selector, context));
}

export function find(selector, context) {
    return toNode(_query(selector, context, 'querySelector'));
}

export function findAll(selector, context) {
    return toNodes(_query(selector, context, 'querySelectorAll'));
}

const contextSelectorRe = /(^|[^\\],)\s*[!>+~-]/;
const isContextSelector = memoize((selector) => selector.match(contextSelectorRe));

function getContext(selector, context = document) {
    return (isString(selector) && isContextSelector(selector)) || isDocument(context)
        ? context
        : context.ownerDocument;
}

const contextSanitizeRe = /([!>+~-])(?=\s+[!>+~-]|\s*$)/g;

function _query(selector, context = document, queryFn) {
    if (!selector || !isString(selector)) {
        return selector;
    }

    selector = selector.replace(contextSanitizeRe, '$1 *');

    if (isContextSelector(selector)) {
        selector = splitSelector(selector)
            .map((selector) => {
                let ctx = context;

                if (selector[0] === '!') {
                    const selectors = selector.substr(1).trim().split(' ');
                    ctx = closest(parent(context), selectors[0]);
                    selector = selectors.slice(1).join(' ').trim();
                }

                if (selector[0] === '-') {
                    const selectors = selector.substr(1).trim().split(' ');
                    const prev = (ctx || context).previousElementSibling;
                    ctx = matches(prev, selector.substr(1)) ? prev : null;
                    selector = selectors.slice(1).join(' ');
                }

                if (!ctx) {
                    return null;
                }

                return `${domPath(ctx)} ${selector}`;
            })
            .filter(Boolean)
            .join(',');

        context = document;
    }

    try {
        return context[queryFn](selector);
    } catch (e) {
        return null;
    }
}

const selectorRe = /.*?[^\\](?:,|$)/g;

const splitSelector = memoize((selector) =>
    selector.match(selectorRe).map((selector) => selector.replace(/,$/, '').trim())
);

function domPath(element) {
    const names = [];
    while (element.parentNode) {
        const id = attr(element, 'id');
        if (id) {
            names.unshift(`#${escape(id)}`);
            break;
        } else {
            let { tagName } = element;
            if (tagName !== 'HTML') {
                tagName += `:nth-child(${index(element) + 1})`;
            }
            names.unshift(tagName);
            element = element.parentNode;
        }
    }
    return names.join(' > ');
}

export function escape(css) {
    return isString(css) ? CSS.escape(css) : '';
}
<commit_msg>perf: query shortcuts simple `!*` selectors<commit_after>import { attr } from './attr';
import { closest, index, matches, parent } from './filter';
import { isDocument, isString, memoize, toNode, toNodes } from './lang';

export function query(selector, context) {
    return find(selector, getContext(selector, context));
}

export function queryAll(selector, context) {
    return findAll(selector, getContext(selector, context));
}

export function find(selector, context) {
    return toNode(_query(selector, context, 'querySelector'));
}

export function findAll(selector, context) {
    return toNodes(_query(selector, context, 'querySelectorAll'));
}

const contextSelectorRe = /(^|[^\\],)\s*[!>+~-]/;
const isContextSelector = memoize((selector) => selector.match(contextSelectorRe));

function getContext(selector, context = document) {
    return (isString(selector) && isContextSelector(selector)) || isDocument(context)
        ? context
        : context.ownerDocument;
}

const contextSanitizeRe = /([!>+~-])(?=\s+[!>+~-]|\s*$)/g;
const sanatize = memoize((selector) => selector.replace(contextSanitizeRe, '$1 *'));

function _query(selector, context = document, queryFn) {
    if (!selector || !isString(selector)) {
        return selector;
    }

    selector = sanatize(selector);

    if (isContextSelector(selector)) {
        const split = splitSelector(selector);
        selector = '';
        for (let sel of split) {
            let ctx = context;

            if (sel[0] === '!') {
                const selectors = sel.substr(1).trim().split(' ');
                ctx = closest(parent(context), selectors[0]);
                sel = selectors.slice(1).join(' ').trim();
                if (!sel.length && split.length === 1) {
                    return ctx;
                }
            }

            if (sel[0] === '-') {
                const selectors = sel.substr(1).trim().split(' ');
                const prev = (ctx || context).previousElementSibling;
                ctx = matches(prev, sel.substr(1)) ? prev : null;
                sel = selectors.slice(1).join(' ');
            }

            if (ctx) {
                selector += `${selector ? ',' : ''}${domPath(ctx)} ${sel}`;
            }
        }

        context = document;
    }

    try {
        return context[queryFn](selector);
    } catch (e) {
        return null;
    }
}

const selectorRe = /.*?[^\\](?:,|$)/g;

const splitSelector = memoize((selector) =>
    selector.match(selectorRe).map((selector) => selector.replace(/,$/, '').trim())
);

function domPath(element) {
    const names = [];
    while (element.parentNode) {
        const id = attr(element, 'id');
        if (id) {
            names.unshift(`#${escape(id)}`);
            break;
        } else {
            let { tagName } = element;
            if (tagName !== 'HTML') {
                tagName += `:nth-child(${index(element) + 1})`;
            }
            names.unshift(tagName);
            element = element.parentNode;
        }
    }
    return names.join(' > ');
}

export function escape(css) {
    return isString(css) ? CSS.escape(css) : '';
}
<|endoftext|>


<commit_before>var bitcore = module.exports;

//main bitcoin library
bitcore.Address = require('./lib/address');
bitcore.Base58 = require('./lib/base58');
bitcore.Base58Check = require('./lib/base58check');
bitcore.BIP32 = require('./lib/bip32');
bitcore.BN = require('./lib/bn');
bitcore.BufferReader = require('./lib/bufferreader');
bitcore.BufferWriter = require('./lib/bufferwriter');
bitcore.Constants = require('./lib/constants');
bitcore.ECDSA = require('./lib/ecdsa');
bitcore.Hash = require('./lib/hash');
bitcore.KDF = require('./lib/kdf');
bitcore.Keypair = require('./lib/keypair');
bitcore.Message = require('./lib/message');
bitcore.Opcode = require('./lib/opcode');
bitcore.Point = require('./lib/point');
bitcore.Privkey = require('./lib/privkey');
bitcore.Pubkey = require('./lib/pubkey');
bitcore.Random = require('./lib/random');
bitcore.Script = require('./lib/script');
bitcore.Signature = require('./lib/signature');
bitcore.Varint = require('./lib/varint');

//experimental, nonstandard, or unstable features
bitcore.expmt = {};
bitcore.expmt.AES = require('./lib/expmt/aes');
bitcore.expmt.AESCBC = require('./lib/expmt/aescbc');
bitcore.expmt.CBC = require('./lib/expmt/cbc');
bitcore.expmt.ECIES = require('./lib/expmt/ecies');
bitcore.expmt.StealthAddress = require('./lib/expmt/stealthaddress');
bitcore.expmt.Stealthkey = require('./lib/expmt/stealthkey');
bitcore.expmt.StealthMessage = require('./lib/expmt/stealthmessage');

//dependencies, subject to change
bitcore.deps = {};
bitcore.deps.aes = require('aes');
bitcore.deps.bnjs = require('bn.js');
bitcore.deps.bs58 = require('bs58');
bitcore.deps.Buffer = Buffer;
bitcore.deps.elliptic = require('elliptic');
bitcore.deps.hashjs = require('hash.js');
bitcore.deps.sha512 = require('sha512');

//bitcore.scriptexec = require('lib/scriptexec');
//bitcore.tx = require('lib/tx');
//bitcore.txpartial = require('lib/txpartial');

//bitcore.bip70 = require('lib/bip70');
<commit_msg>expose<commit_after>var bitcore = module.exports;

//main bitcoin library
bitcore.Address = require('./lib/address');
bitcore.Base58 = require('./lib/base58');
bitcore.Base58Check = require('./lib/base58check');
bitcore.BIP32 = require('./lib/bip32');
bitcore.Block = require('./lib/block');
bitcore.Blockheader = require('./lib/blockheader');
bitcore.BN = require('./lib/bn');
bitcore.BufferReader = require('./lib/bufferreader');
bitcore.BufferWriter = require('./lib/bufferwriter');
bitcore.Constants = require('./lib/constants');
bitcore.ECDSA = require('./lib/ecdsa');
bitcore.Hash = require('./lib/hash');
bitcore.KDF = require('./lib/kdf');
bitcore.Keypair = require('./lib/keypair');
bitcore.Message = require('./lib/message');
bitcore.Opcode = require('./lib/opcode');
bitcore.Point = require('./lib/point');
bitcore.Privkey = require('./lib/privkey');
bitcore.Pubkey = require('./lib/pubkey');
bitcore.Random = require('./lib/random');
bitcore.Script = require('./lib/script');
bitcore.Signature = require('./lib/signature');
bitcore.Transaction = require('./lib/transaction');
bitcore.Txin = require('./lib/txin');
bitcore.Txout = require('./lib/txout');
bitcore.Varint = require('./lib/varint');

//experimental, nonstandard, or unstable features
bitcore.expmt = {};
bitcore.expmt.AES = require('./lib/expmt/aes');
bitcore.expmt.AESCBC = require('./lib/expmt/aescbc');
bitcore.expmt.CBC = require('./lib/expmt/cbc');
bitcore.expmt.ECIES = require('./lib/expmt/ecies');
bitcore.expmt.StealthAddress = require('./lib/expmt/stealthaddress');
bitcore.expmt.Stealthkey = require('./lib/expmt/stealthkey');
bitcore.expmt.StealthMessage = require('./lib/expmt/stealthmessage');

//dependencies, subject to change
bitcore.deps = {};
bitcore.deps.aes = require('aes');
bitcore.deps.bnjs = require('bn.js');
bitcore.deps.bs58 = require('bs58');
bitcore.deps.Buffer = Buffer;
bitcore.deps.elliptic = require('elliptic');
bitcore.deps.hashjs = require('hash.js');
bitcore.deps.sha512 = require('sha512');

//bitcore.scriptexec = require('lib/scriptexec');
//bitcore.tx = require('lib/tx');
//bitcore.txpartial = require('lib/txpartial');

//bitcore.bip70 = require('lib/bip70');
<|endoftext|>


<commit_before>'use strict';

import _ from 'lodash';
import getSlug from 'speakingurl';
import moment from 'moment';
import 'moment-duration-format';

//-----------------------------------
// Utility Functions
//-----------------------------------
class Helpers {

  // Add Ellipsis to the given text
  //--------------
  static dotdotdot(text) {
    return _.truncate(text, {
      length: 180,          // How many characters are shown by default
      separator: /\W?\s/,   // Truncate by word boundary.
      omission: '…'         // …
    });
  }

  // Format time-length
  //--------------
  static formatDuration(runtime) {
    return moment
    .duration(parseInt(runtime, 10), 'minutes')
    .format('h [hrs], m [min]');
  }

  // Modify the URL of the image (for testing purposes)
  //--------------
  static imagify(Poster) {

    // Used for Local Images
    //--------------
    const path = '/img/movies-posters/';
    const poster = Poster.replace(/https:\/\/images-na.ssl-images-amazon.com\/images\/M\//, '');

    // Used for Development Purposes
    return path + poster;

    // Used for External Images
    //--------------
    // return Poster;
  }

  // Replace Special characters for the given text
  //--------------
  /*
  static normalize(text) {
    if (!text) return '';

    return text
    .replace(/\W+/g, '')
    .toLowerCase();
  }
  */

  // Convert Text string into url-slug
  //--------------
  // https://www.npmjs.com/package/speakingurl
  static slugify(text) {
    // return text
    // .replace(/\s+/g, '-')
    // .replace(/[^a-z0-9 -]/gi, '')
    // .replace(/[ÀÁÂÃÄÅ]/g,"A")
    // .replace(/[àáâãäå]/g,"a")
    // .toLowerCase();
    return getSlug(text);
    // return _.kebabCase(text);
  }

  /*
  static partition(data, n) {
    return _.chain(data)
    .groupBy(function (element, index) {
      return Math.floor(index / n);
    }).toArray().value();
  }
  */
}

export default Helpers;
<commit_msg>Use a different image source in Dev & Prod environments<commit_after>'use strict';

import _ from 'lodash';
import getSlug from 'speakingurl';
import moment from 'moment';
import 'moment-duration-format';

//-----------------------------------
// Utility Functions
//-----------------------------------
class Helpers {

  // Add Ellipsis to the given text
  //--------------
  static dotdotdot(text) {
    return _.truncate(text, {
      length: 180,          // How many characters are shown by default
      separator: /\W?\s/,   // Truncate by word boundary.
      omission: '…'         // …
    });
  }

  // Format time-length
  //--------------
  static formatDuration(runtime) {
    return moment
    .duration(parseInt(runtime, 10), 'minutes')
    .format('h [hrs], m [min]');
  }

  // Modify the URL of the image (for testing purposes)
  //--------------
  static imagify(Poster) {

    // Used for Local Images
    //--------------
    const path = '/img/movies-posters/';
    const poster = Poster.replace(/https:\/\/images-na.ssl-images-amazon.com\/images\/M\//, '');

    // Used for Development Purposes
    //--------------
    if (process.env.NODE_ENV !== 'production') {
      return path + poster;
    }

    // Used for External Images
    return Poster;

  }

  // Replace Special characters for the given text
  //--------------
  /*
  static normalize(text) {
    if (!text) return '';

    return text
    .replace(/\W+/g, '')
    .toLowerCase();
  }
  */

  // Convert Text string into url-slug
  //--------------
  // https://www.npmjs.com/package/speakingurl
  static slugify(text) {
    // return text
    // .replace(/\s+/g, '-')
    // .replace(/[^a-z0-9 -]/gi, '')
    // .replace(/[ÀÁÂÃÄÅ]/g,"A")
    // .replace(/[àáâãäå]/g,"a")
    // .toLowerCase();
    return getSlug(text);
    // return _.kebabCase(text);
  }

  /*
  static partition(data, n) {
    return _.chain(data)
    .groupBy(function (element, index) {
      return Math.floor(index / n);
    }).toArray().value();
  }
  */
}

export default Helpers;
<|endoftext|>


<commit_before>var path = require('path');
var through2 = require('through2');
var PluginError = require('gulp-util').PluginError;
var colors = require('gulp-util').colors;
var log = require('gulp-util').log;
var QN = require('qn');
var Moment = require('moment');
var Q = require('q');
var fs = require('fs')
var crypto = require('crypto')
var minimatch = require('minimatch')
var uploadedFiles = 0;
var getEtag = require('./qetag')

module.exports = function (qiniu, option) {
  option = option || {};
  option = extend({dir: '', versioning: false, versionFile: null, ignore: ['*.html'], concurrent: 10}, option);

  var qn = QN.create(qiniu)
    , version = Moment().format('YYMMDDHHmm')
    , qs = []
    , filesIndex = 0

  return through2.obj(function (file, enc, next) {
    var that = this;
    var isIgnore = false;
    var filePath = path.relative(file.base, file.path);
    filePath = filePath.split(path.sep).join('/');

    if (file._contents === null) return next();
    option.ignore.forEach(function (item) {
      if (minimatch(filePath, item)) isIgnore = true;
    })
    if (isIgnore) return next();

    filesIndex++

    var fileKey = option.dir + ((!option.dir || option.dir[option.dir.length - 1]) === '/' ? '' : '/') + (option.versioning ? version + '/' : '') + filePath;
    var retries = 0;
    var isConcurrent = filesIndex % Math.floor(option.concurrent) !== 0

    var handler = function () {
      return Q.nbind(qn.stat, qn)(fileKey)
        .spread(function (stat) {
          return Q.nfcall(getEtag, file._contents)
            .then(function (fileHash) {
              // Skip when hash equal
              if (stat.hash === fileHash) return false;

              // Start
              log('Start →', fileKey);

              // Then delete
              return Q.nbind(qn.delete, qn)(fileKey)
            })
        }, function () {
          // Start
          log('Start →', fileKey);

          // Upload when not exists
          return true;
        })
        .then(function (isUpload) {
          if (isUpload === false) return false;
          return Q.nbind(qn.upload, qn)(file._contents, {key: fileKey})
        })
        .then(function (stat) {
          // No upload
          if (stat === false) {
            log('Skip →', colors.grey(fileKey));
            !isConcurrent && next()
            return;
          }

          // Record hash
          uploadedFiles++;

          log('Upload →', colors.green(qiniu.origin + '/' + fileKey));
          !isConcurrent && next()
        }, function (err) {
          log('Error →', colors.red(fileKey), new PluginError('gulp-qiniu', err).message);
          that.emit('Error', colors.red(fileKey), new PluginError('gulp-qiniu', err));

          if (retries++ < 3) {
            log('Retry(' + retries + ') →', colors.red(fileKey));
            return handler()
          } else {
            !isConcurrent && next()
          }
        })
    }

    qs.push(handler())

    isConcurrent && next()
  }, function () {
    Q.all(qs)
      .then(function (rets) {
        log('Total →', colors.green(uploadedFiles + '/' + rets.length));

        // Check if versioning
        if (!option.versioning) return;
        log('Version →', colors.green(version));

        if (option.versionFile) {
          fs.writeFileSync(option.versionFile, JSON.stringify({version: version}))
          log('Write version file →', colors.green(option.versionFile));
        }
      }, function (err) {
        log('Failed upload →', err.message);
      });
  });

  function extend(target, source) {
    target = target || {};
    for (var prop in source) {
      if (typeof source[prop] === 'object') {
        target[prop] = extend(target[prop], source[prop]);
      } else {
        target[prop] = source[prop];
      }
    }
    return target;
  }
};
<commit_msg>删除qiniu.origin + '/' 配置环境里面不需要配置origin<commit_after>var path = require('path');
var through2 = require('through2');
var PluginError = require('gulp-util').PluginError;
var colors = require('gulp-util').colors;
var log = require('gulp-util').log;
var QN = require('qn');
var Moment = require('moment');
var Q = require('q');
var fs = require('fs')
var crypto = require('crypto')
var minimatch = require('minimatch')
var uploadedFiles = 0;
var getEtag = require('./qetag')

module.exports = function (qiniu, option) {
  option = option || {};
  option = extend({dir: '', versioning: false, versionFile: null, ignore: ['*.html'], concurrent: 10}, option);

  var qn = QN.create(qiniu)
    , version = Moment().format('YYMMDDHHmm')
    , qs = []
    , filesIndex = 0

  return through2.obj(function (file, enc, next) {
    var that = this;
    var isIgnore = false;
    var filePath = path.relative(file.base, file.path);
    filePath = filePath.split(path.sep).join('/');

    if (file._contents === null) return next();
    option.ignore.forEach(function (item) {
      if (minimatch(filePath, item)) isIgnore = true;
    })
    if (isIgnore) return next();

    filesIndex++

    var fileKey = option.dir + ((!option.dir || option.dir[option.dir.length - 1]) === '/' ? '' : '/') + (option.versioning ? version + '/' : '') + filePath;
    var retries = 0;
    var isConcurrent = filesIndex % Math.floor(option.concurrent) !== 0

    var handler = function () {
      return Q.nbind(qn.stat, qn)(fileKey)
        .spread(function (stat) {
          return Q.nfcall(getEtag, file._contents)
            .then(function (fileHash) {
              // Skip when hash equal
              if (stat.hash === fileHash) return false;

              // Start
              log('Start →', fileKey);

              // Then delete
              return Q.nbind(qn.delete, qn)(fileKey)
            })
        }, function () {
          // Start
          log('Start →', fileKey);

          // Upload when not exists
          return true;
        })
        .then(function (isUpload) {
          if (isUpload === false) return false;
          return Q.nbind(qn.upload, qn)(file._contents, {key: fileKey})
        })
        .then(function (stat) {
          // No upload
          if (stat === false) {
            log('Skip →', colors.grey(fileKey));
            !isConcurrent && next()
            return;
          }

          // Record hash
          uploadedFiles++;

          log('Upload →', colors.green(fileKey));
          !isConcurrent && next()
        }, function (err) {
          log('Error →', colors.red(fileKey), new PluginError('gulp-qiniu', err).message);
          that.emit('Error', colors.red(fileKey), new PluginError('gulp-qiniu', err));

          if (retries++ < 3) {
            log('Retry(' + retries + ') →', colors.red(fileKey));
            return handler()
          } else {
            !isConcurrent && next()
          }
        })
    }

    qs.push(handler())

    isConcurrent && next()
  }, function () {
    Q.all(qs)
      .then(function (rets) {
        log('Total →', colors.green(uploadedFiles + '/' + rets.length));

        // Check if versioning
        if (!option.versioning) return;
        log('Version →', colors.green(version));

        if (option.versionFile) {
          fs.writeFileSync(option.versionFile, JSON.stringify({version: version}))
          log('Write version file →', colors.green(option.versionFile));
        }
      }, function (err) {
        log('Failed upload →', err.message);
      });
  });

  function extend(target, source) {
    target = target || {};
    for (var prop in source) {
      if (typeof source[prop] === 'object') {
        target[prop] = extend(target[prop], source[prop]);
      } else {
        target[prop] = source[prop];
      }
    }
    return target;
  }
};
<|endoftext|>