<commit_before>'use strict';

var Buffer = require('safe-buffer').Buffer;
var qs = require('qs');
var crypto = require('crypto');

var hasOwn = {}.hasOwnProperty;
var isPlainObject = require('lodash.isplainobject');

var OPTIONS_KEYS = ['api_key', 'idempotency_key', 'stripe_account', 'stripe_version'];

var utils = module.exports = {

  isAuthKey: function(key) {
    return typeof key == 'string' && /^(?:[a-z]{2}_)?[A-z0-9]{32}$/.test(key);
  },

  isOptionsHash: function(o) {
    return isPlainObject(o) && OPTIONS_KEYS.some(function(key) {
      return hasOwn.call(o, key);
    });
  },

  /**
   * Stringifies an Object, accommodating nested objects
   * (forming the conventional key 'parent[child]=value')
   */
  stringifyRequestData: function(data) {
    return qs.stringify(data, {arrayFormat: 'brackets'});
  },

  /**
   * Outputs a new function with interpolated object property values.
   * Use like so:
   *   var fn = makeURLInterpolator('some/url/{param1}/{param2}');
   *   fn({ param1: 123, param2: 456 }); // => 'some/url/123/456'
   */
  makeURLInterpolator: (function() {
    var rc = {
      '\n': '\\n', '\"': '\\\"',
      '\u2028': '\\u2028', '\u2029': '\\u2029',
    };
    return function makeURLInterpolator(str) {
      var cleanString = str.replace(/["\n\r\u2028\u2029]/g, function($0) {
        return rc[$0];
      });
      return function(outputs) {
        return cleanString.replace(/\{([\s\S]+?)\}/g, function($0, $1) {
          return encodeURIComponent(outputs[$1] || '');
        });
      };
    };
  }()),

  /**
   * Return the data argument from a list of arguments
   */
  getDataFromArgs: function(args) {
    if (args.length < 1 || !isPlainObject(args[0])) {
      return {};
    }

    if (!utils.isOptionsHash(args[0])) {
      return args.shift();
    }

    var argKeys = Object.keys(args[0]);

    var optionKeysInArgs = argKeys.filter(function(key) {
      return OPTIONS_KEYS.indexOf(key) > -1;
    });

    // We need to be sure that it's not _both_ an options and args object:
    // If the args keys and options keys are not the same length,
    // then it contains both arguments and options, and that's not good.
    if (argKeys.length !== optionKeysInArgs.length) {
      console.warn(
        'Stripe: Options found in arguments (' + optionKeysInArgs.join(', ') + '). Did you mean to pass an options ' +
        'object? See https://github.com/stripe/stripe-node/wiki/Passing-Options.'
      );
    }

    return {};
  },

  /**
   * Return the options hash from a list of arguments
   */
  getOptionsFromArgs: function(args) {
    var opts = {
      auth: null,
      headers: {},
    }
    if (args.length > 0) {
      var arg = args[args.length - 1];
      if (utils.isAuthKey(arg)) {
        opts.auth = args.pop();
      } else if (utils.isOptionsHash(arg)) {
        var params = args.pop();
        if (params.api_key) {
          opts.auth = params.api_key;
        }
        if (params.idempotency_key) {
          opts.headers['Idempotency-Key'] = params.idempotency_key;
        }
        if (params.stripe_account) {
          opts.headers['Stripe-Account'] = params.stripe_account;
        }
        if (params.stripe_version) {
          opts.headers['Stripe-Version'] = params.stripe_version;
        }
      }
    }
    return opts;
  },

  /**
   * Provide simple "Class" extension mechanism
   */
  protoExtend: function(sub) {
    var Super = this;
    var Constructor = hasOwn.call(sub, 'constructor') ? sub.constructor : function() {
      Super.apply(this, arguments);
    };

    // This initialization logic is somewhat sensitive to be compatible with
    // divergent JS implementations like the one found in Qt. See here for more
    // context:
    //
    // https://github.com/stripe/stripe-node/pull/334
    Object.assign(Constructor, Super);
    Constructor.prototype = Object.create(Super.prototype);
    Object.assign(Constructor.prototype, sub);

    return Constructor;
  },

  /**
   * Convert an array into an object with integer string attributes
   */
  arrayToObject: function(arr) {
    if (Array.isArray(arr)) {
      var obj = {};
      arr.map(function(item, i) {
        obj[i.toString()] = item;
      });
      return obj;
    }
    return arr;
  },

  /**
  * Secure compare, from https://github.com/freewil/scmp
  */
  secureCompare: function(a, b) {
    var a = Buffer.from(a);
    var b = Buffer.from(b);

    // return early here if buffer lengths are not equal since timingSafeEqual
    // will throw if buffer lengths are not equal
    if (a.length !== b.length) {
      return false;
    }

    // use crypto.timingSafeEqual if available (since Node.js v6.6.0),
    // otherwise use our own scmp-internal function.
    if (crypto.timingSafeEqual) {
      return crypto.timingSafeEqual(a, b);
    }

    var len = a.length;
    var result = 0;

    for (var i = 0; i < len; ++i) {
      result |= a[i] ^ b[i];
    }
    return result === 0;
  },

  /**
  * Remove empty values from an object
  */
  removeEmpty: function(obj) {
    if (typeof obj !== 'object') {
      throw new Error('Argument must be an object');
    }

    Object.keys(obj).forEach(function(key) {
      if (obj[key] === null || obj[key] === undefined) {
        delete obj[key];
      }
    });

    return obj;
  },
};
<commit_msg>Make condition more explicit and update comment<commit_after>'use strict';

var Buffer = require('safe-buffer').Buffer;
var qs = require('qs');
var crypto = require('crypto');

var hasOwn = {}.hasOwnProperty;
var isPlainObject = require('lodash.isplainobject');

var OPTIONS_KEYS = ['api_key', 'idempotency_key', 'stripe_account', 'stripe_version'];

var utils = module.exports = {

  isAuthKey: function(key) {
    return typeof key == 'string' && /^(?:[a-z]{2}_)?[A-z0-9]{32}$/.test(key);
  },

  isOptionsHash: function(o) {
    return isPlainObject(o) && OPTIONS_KEYS.some(function(key) {
      return hasOwn.call(o, key);
    });
  },

  /**
   * Stringifies an Object, accommodating nested objects
   * (forming the conventional key 'parent[child]=value')
   */
  stringifyRequestData: function(data) {
    return qs.stringify(data, {arrayFormat: 'brackets'});
  },

  /**
   * Outputs a new function with interpolated object property values.
   * Use like so:
   *   var fn = makeURLInterpolator('some/url/{param1}/{param2}');
   *   fn({ param1: 123, param2: 456 }); // => 'some/url/123/456'
   */
  makeURLInterpolator: (function() {
    var rc = {
      '\n': '\\n', '\"': '\\\"',
      '\u2028': '\\u2028', '\u2029': '\\u2029',
    };
    return function makeURLInterpolator(str) {
      var cleanString = str.replace(/["\n\r\u2028\u2029]/g, function($0) {
        return rc[$0];
      });
      return function(outputs) {
        return cleanString.replace(/\{([\s\S]+?)\}/g, function($0, $1) {
          return encodeURIComponent(outputs[$1] || '');
        });
      };
    };
  }()),

  /**
   * Return the data argument from a list of arguments
   */
  getDataFromArgs: function(args) {
    if (args.length < 1 || !isPlainObject(args[0])) {
      return {};
    }

    if (!utils.isOptionsHash(args[0])) {
      return args.shift();
    }

    var argKeys = Object.keys(args[0]);

    var optionKeysInArgs = argKeys.filter(function(key) {
      return OPTIONS_KEYS.indexOf(key) > -1;
    });

    // In some cases options may be the provided as the first argument.
    // Here we're detecting a case where there are two distinct arguments
    // (the first being args and the second options) and with known
    // option keys in the first so that we can warn the user about it.
    if (optionKeysInArgs.length > 0 && optionKeysInArgs.length !== argKeys.length) {
      console.warn(
        'Stripe: Options found in arguments (' + optionKeysInArgs.join(', ') + '). Did you mean to pass an options ' +
        'object? See https://github.com/stripe/stripe-node/wiki/Passing-Options.'
      );
    }

    return {};
  },

  /**
   * Return the options hash from a list of arguments
   */
  getOptionsFromArgs: function(args) {
    var opts = {
      auth: null,
      headers: {},
    }
    if (args.length > 0) {
      var arg = args[args.length - 1];
      if (utils.isAuthKey(arg)) {
        opts.auth = args.pop();
      } else if (utils.isOptionsHash(arg)) {
        var params = args.pop();
        if (params.api_key) {
          opts.auth = params.api_key;
        }
        if (params.idempotency_key) {
          opts.headers['Idempotency-Key'] = params.idempotency_key;
        }
        if (params.stripe_account) {
          opts.headers['Stripe-Account'] = params.stripe_account;
        }
        if (params.stripe_version) {
          opts.headers['Stripe-Version'] = params.stripe_version;
        }
      }
    }
    return opts;
  },

  /**
   * Provide simple "Class" extension mechanism
   */
  protoExtend: function(sub) {
    var Super = this;
    var Constructor = hasOwn.call(sub, 'constructor') ? sub.constructor : function() {
      Super.apply(this, arguments);
    };

    // This initialization logic is somewhat sensitive to be compatible with
    // divergent JS implementations like the one found in Qt. See here for more
    // context:
    //
    // https://github.com/stripe/stripe-node/pull/334
    Object.assign(Constructor, Super);
    Constructor.prototype = Object.create(Super.prototype);
    Object.assign(Constructor.prototype, sub);

    return Constructor;
  },

  /**
   * Convert an array into an object with integer string attributes
   */
  arrayToObject: function(arr) {
    if (Array.isArray(arr)) {
      var obj = {};
      arr.map(function(item, i) {
        obj[i.toString()] = item;
      });
      return obj;
    }
    return arr;
  },

  /**
  * Secure compare, from https://github.com/freewil/scmp
  */
  secureCompare: function(a, b) {
    var a = Buffer.from(a);
    var b = Buffer.from(b);

    // return early here if buffer lengths are not equal since timingSafeEqual
    // will throw if buffer lengths are not equal
    if (a.length !== b.length) {
      return false;
    }

    // use crypto.timingSafeEqual if available (since Node.js v6.6.0),
    // otherwise use our own scmp-internal function.
    if (crypto.timingSafeEqual) {
      return crypto.timingSafeEqual(a, b);
    }

    var len = a.length;
    var result = 0;

    for (var i = 0; i < len; ++i) {
      result |= a[i] ^ b[i];
    }
    return result === 0;
  },

  /**
  * Remove empty values from an object
  */
  removeEmpty: function(obj) {
    if (typeof obj !== 'object') {
      throw new Error('Argument must be an object');
    }

    Object.keys(obj).forEach(function(key) {
      if (obj[key] === null || obj[key] === undefined) {
        delete obj[key];
      }
    });

    return obj;
  },
};
<|endoftext|>


<commit_before>var fs = require('fs'),
    path = require('path');

module.exports.formatStringForUID = function (content) {

    content = String(content).toLowerCase();

    content = content.replace(/[^\w\.]+/g, '-');
    content = content.replace(/^-|-$/g, '');

    return content;

};

module.exports.parseData = function (data, file) {

    data.forEach(function (methods) {

        if (methods.ctx) {

            methods.ctx.uid = file + '-' + module.exports.formatStringForUID(methods.ctx.string);

        }

        if (methods.tags) {

            methods.tags.forEach(function (tag) {

                if (tag.types) {

                    tag.types.forEach(function (type, index) {

                        if (type.match(/\?/g)) {

                            tag.optional = true;
                            tag.types[index] = type.replace(/\?/g, '');

                        }

                    });

                }

            });

        }

    });

    return data;

};

module.exports.walk = function (dir, opts) {

    var files = [];

    if (!opts) {
        opts = {};
    }

    if (!opts.match) {
        opts.match = /\.js$/;
    }

    if (!opts.exception) {
        opts.exception = /\.git|\.min|node_modules|test|gruntfile/i;
    }

    if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) {

        fs.readdirSync(dir).forEach(function (file) {

            var stat;

            file = path.normalize(dir + '/' + file);

            stat = fs.statSync(file);

            if (stat.isDirectory() && !file.match(opts.exception)) {

                files = files.concat(module.exports.walk(file, opts));

            } else if (stat.isFile() && file.match(opts.match) && !file.match(opts.exception)) {

                files.push(file);

            }

        });

    }

    return files;

};
<commit_msg>Added bower_components and gulpfile to walk exceptions.<commit_after>var fs = require('fs'),
    path = require('path');

module.exports.formatStringForUID = function (content) {

    content = String(content).toLowerCase();

    content = content.replace(/[^\w\.]+/g, '-');
    content = content.replace(/^-|-$/g, '');

    return content;

};

module.exports.parseData = function (data, file) {

    data.forEach(function (methods) {

        if (methods.ctx) {

            methods.ctx.uid = file + '-' + module.exports.formatStringForUID(methods.ctx.string);

        }

        if (methods.tags) {

            methods.tags.forEach(function (tag) {

                if (tag.types) {

                    tag.types.forEach(function (type, index) {

                        if (type.match(/\?/g)) {

                            tag.optional = true;
                            tag.types[index] = type.replace(/\?/g, '');

                        }

                    });

                }

            });

        }

    });

    return data;

};

module.exports.walk = function (dir, opts) {

    var files = [];

    if (!opts) {
        opts = {};
    }

    if (!opts.match) {
        opts.match = /\.js$/;
    }

    if (!opts.exception) {
        opts.exception = /\.git|\.min|node_modules|bower_components|test|gruntfile|gulpfile/i;
    }

    if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) {

        fs.readdirSync(dir).forEach(function (file) {

            var stat;

            file = path.normalize(dir + '/' + file);

            stat = fs.statSync(file);

            if (stat.isDirectory() && !file.match(opts.exception)) {

                files = files.concat(module.exports.walk(file, opts));

            } else if (stat.isFile() && file.match(opts.match) && !file.match(opts.exception)) {

                files.push(file);

            }

        });

    }

    return files;

};
<|endoftext|>


<commit_before>
/**
 * Formats mongoose errors into proper array
 *
 * @param {Array} errors
 * @return {Array}
 * @api public
 */

exports.errors = function (errors) {
  var keys = Object.keys(errors)
  var errs = []

  // if there is no validation error, just display a generic error
  if (!keys) {
    return ['Oops! There was an error']
  }

  keys.forEach(function (key) {
    errs.push(errors[key].message)
  })

  return errs
}

/**
 * Index of object within an array
 *
 * @param {Array} arr
 * @param {Object} obj
 * @return {Number}
 * @api public
 */

exports.indexof = function (arr, obj) {
  var index = -1; // not found initially
  var keys = Object.keys(obj);
  // filter the collection with the given criterias
  var result = arr.filter(function (doc, idx) {
    // keep a counter of matched key/value pairs
    var matched = 0;

    // loop over criteria
    for (var i = keys.length - 1; i >= 0; i--) {
      if (doc[keys[i]] === obj[keys[i]]) {
        matched++;

        // check if all the criterias are matched
        if (matched === keys.length) {
          index = idx;
          return idx;
        }
      }
    };
  });
  return index;
}

/**
 * Find object in an array of objects that matches a condition
 *
 * @param {Array} arr
 * @param {Object} obj
 * @param {Function} cb - optional
 * @return {Object}
 * @api public
 */

exports.findByParam = function (arr, obj, cb) {
  var index = exports.indexof(arr, obj)
  if (~index && typeof cb === 'function') {
    return cb(undefined, arr[index])
  } else if (~index && !cb) {
    return arr[index]
  } else if (!~index && typeof cb === 'function') {
    return cb('not found')
  }
  // else undefined is returned
}
<commit_msg>don't crash when image upload fails<commit_after>
/**
 * Formats mongoose errors into proper array
 *
 * @param {Array} errors
 * @return {Array}
 * @api public
 */

exports.errors = function (errors) {
  var keys = Object.keys(errors)
  var errs = []

  // if there is no validation error, just display a generic error
  if (!keys) {
    return ['Oops! There was an error']
  }

  keys.forEach(function (key) {
    if (errors[key]) errs.push(errors[key].message)
  })

  return errs
}

/**
 * Index of object within an array
 *
 * @param {Array} arr
 * @param {Object} obj
 * @return {Number}
 * @api public
 */

exports.indexof = function (arr, obj) {
  var index = -1; // not found initially
  var keys = Object.keys(obj);
  // filter the collection with the given criterias
  var result = arr.filter(function (doc, idx) {
    // keep a counter of matched key/value pairs
    var matched = 0;

    // loop over criteria
    for (var i = keys.length - 1; i >= 0; i--) {
      if (doc[keys[i]] === obj[keys[i]]) {
        matched++;

        // check if all the criterias are matched
        if (matched === keys.length) {
          index = idx;
          return idx;
        }
      }
    };
  });
  return index;
}

/**
 * Find object in an array of objects that matches a condition
 *
 * @param {Array} arr
 * @param {Object} obj
 * @param {Function} cb - optional
 * @return {Object}
 * @api public
 */

exports.findByParam = function (arr, obj, cb) {
  var index = exports.indexof(arr, obj)
  if (~index && typeof cb === 'function') {
    return cb(undefined, arr[index])
  } else if (~index && !cb) {
    return arr[index]
  } else if (!~index && typeof cb === 'function') {
    return cb('not found')
  }
  // else undefined is returned
}
<|endoftext|>


<commit_before>/*globals qg*/
(function( $, qg ) {
	'use strict';

	// assume: qg.swe, qg.data.get

	// add data to a page as text, a list or chart
	qg.swe.loaded( 'data', function( config ) {
		config = $.extend({
			title: 'Data',
			headings: [],
			rowHeadings: true,
			group: 'col', // note: anything other than 'row' becomes col anyway
			formats: {},
			type: 'bar',
			legend: true,
			supplementary: [],
			colours: [ '#313695', '#4575B4', '#74ADD1', '#ABD9E9', '#FEE090', '#FDAE61', '#F46D43', '#D73027', '#A50026' ],
			delimiter: ', '
		}, config );

		var callback = {};

		// colour keywords
		if ( typeof config.colours === 'string' ) {
			switch ( config.colours.toLowerCase() ) {
			case 'rag':
				config.colours = [ '#d80000', '#ffb400', '#007c00' ];
				break;

			case 'priority':
				config.colours = [ '#F46D43', '#FDAE61', '#313695', '#ABD9E9' ];
				break;

			case 'initiatives':
				config.colours = [ '#313695' ];
				break;
			}
		}

		callback.text = function( data ) {
			var container = $( document.getElementById( config.container ));
			// check container exists
			if ( container.length === 0 ) {
				$.debug( 'Element not found for data', config.container );
				return;
			}

			// add data to page
			if ( data.result.records.length > 0 ) {
				container.html(
					$.map( data.result.records, function( record ) {
						record = $.map( record, function( value, key ) {
							return qg.swe.template.format( value, config.formats[ key ] );
						}).join( config.delimiter );
						return record;
					}).join( '\n' )
				);
			}
			// trigger SWE layout reflow
			container.trigger( 'x-height-change' );
		};

		callback.ul = function( data ) {
			var container = $( document.getElementById( config.container ));

			// check container exists
			if ( container.length === 0 ) {
				$.debug( 'Element not found for data', config.container );
				return;
			}

			// add data to page
			if ( data.result.records.length > 0 ) {
				container.html(
					'
' +
						$.map( data.result.records, function( record ) {
							record = $.map( record, function( value, key ) {
								return qg.swe.template.format( value, config.formats[ key ] );
							}).join( config.delimiter );
							return '
' + qg.swe.template.clean( record ) + '
';
						}).join( '\n' ) +
					'
'
				);
			}
			// trigger SWE layout reflow
			container.trigger( 'x-height-change' );
		};

		callback.dl = function( data ) {
			var container = $( document.getElementById( config.container ));

			// check container exists
			if ( container.length === 0 ) {
				$.debug( 'Element not found for chart', config.container );
				return;
			}

			// add data table to page
			var hideClass = '',
			    rowCount = 0
			;

			container.html( '
Displaying ' + data.result.records.length + ' search results
' +
				$.map( data.result.records, function( rowData, dlHeading ) {
					rowCount++;
					rowData = $.map( config.headings, function( key, i ) {

						hideClass = $.inArray( key, config.supplementary ) > -1 ?  ' class="supplementary"' : '';
						if (i !== 0 ) {
							return '
' + config.headings[ i ] + '

' + qg.swe.template.format( rowData[ key ], config.formats[ key ], { abbr: 'both' } ) + '
';
						} else {
							dlHeading = '
' + qg.swe.template.format( rowData[ key ], config.formats[ key ], { abbr: 'both' } ) + '
';
						}
					});
					return '
' + dlHeading + '
' + rowData.join( '' ) + '

' +
					       ( config.supplementary.length > 0 ? 'Show more…' : '' );
				}).join( '' )
			);

			// setup lightbox
			$( 'a.more', container ).butterfly();

			// trigger SWE layout reflow
			container.trigger( 'x-height-change' );
		};

		callback.chart = function( data ) {
			var container = $( document.getElementById( config.container )),
				even = true,
				table, w, h, rows, cols,
				chart, series, grid, xaxis, yaxis, legend
			;

			// check container exists
			if ( container.length === 0 ) {
				$.debug( 'Element not found for chart', config.container );
				return;
			}

			// add data table to page
			container.html(
				'
' +
					'
' + qg.swe.template.clean( config.title ) + '
' +
					'
' + $.map( config.headings, function( h ) { return '' + qg.swe.template.clean( h ) + '	'; } ).join( '' ) + '
' +
					'
' +
					$.map( data.result.records, function( rowData ) {
						var tagName;
						rowData = $.map( config.headings.slice( 0 ), function( key, i ) {
							tagName = config.rowHeadings && i === 0 ? 'th' : 'td';
							return '<' + tagName + ( tagName === 'th' ? ' scope="row"' : '' ) + '>' +
								   qg.swe.template.format( rowData[ key ], config.formats[ key ] ) + '</' + tagName + '>';
						});
						return '
' + rowData.join( '' ) + '
';
					}).join( '' ) +
					'
' +
				'
'
			);

			// put table into a tab section
			table = container.find( 'table' );
			table.wrap( '
' ).before( '
Table
' );
			table.parent().generateId( config.container + '-table' );
			// get height (this won't change inside tabs)
			// min height of 250px (pie charts broken in FF at 140px height)
			h = Math.max( table.height(), 250 );
			// don't get width here, padding inside tabs will reduce it

			// chart section
			chart = $( '

Chart

' )
				.generateId( config.container + '-chart' )
				.prependTo( container )
			;
			// chart container (within chart section)
			chart = $( '
' ).appendTo( chart );

			// initialise tabs (this is so chart size will be correct)
			container.compact({
				type: 'tabbed',
				variableHeight: true,
				rememberStateInCookie: false
			});

			// get width within tabs
			w = chart.width();
			// set height (required for flot)
			chart.height( h );
			rows = $( 'tbody tr', table ).length;
			cols = $( 'tbody tr', table ).eq( 0 ).find( 'td' ).length; // don't count th

			// TODO make this a plugin that can run on any existing HTML table

			// prep data for chart
			xaxis = {};
			yaxis = {
				tickDecimals: 0,
				max: 1
			};

			// this code works for:
			// - column headings = legend
			// - row headings = xaxis (groups of bars)
			data = $.map( $( 'tbody tr', table ), function( tr ) {
				return [ $.map( $( 'th, td', tr ), function( td ) {
					td = $( td );
					var text = td.text();
					if ( td.is( 'td' )) {
						text = parseFloat( text.replace( /[^0-9\.]+/g, '' ));
					}
					return text;
				})];
			});
			// console.log( 'table data', data );

			switch ( config.type ) {
			case 'line':
				// grouping data
				if ( config.group !== 'row' ) {
					// [ a, b, c ] => { label: a, data: [[ x1, b ], [ x2, c ]] }
					data = $.map( data, function( d ) {
						return {
							label: d.shift(),
							data: $.map( d, function( value, col ) {
								if ( value > yaxis.max ) {
									yaxis.max = value;
								}
								return [[ col, value ]];
							})
						};
					});
					// col headings = x axis labels
					xaxis = {
						tickColor: '#EFEDEE',
						autoscaleMargin: 0.02, // allow space left and right
						ticks: $.map( $( 'thead th', table ).slice( 1 ), function( th, col ) {
							return [[ col, $( th ).text() ]];
						})
					};

				} else {
					// group by row
					// row 1 = [ th, a, b ] -> { label: thead > th, data: [[ x1, a ], [ x2, m ], [ x3, x ]] }
					// row 2 = [ th, m, n ]
					// row 3 = [ th, x, y ]
					// note: assume row headings
					data = $.map( data[ 0 ].slice( 1 ), function( d, col ) {
						return {
							label: $( 'thead th', table ).eq( col + 1 ).text(),
							data: $.map( data, function( rowData, row ) {
								var value = rowData[ col + 1 ];
								if ( value > yaxis.max ) {
									yaxis.max = value;
								}
								return [[ row, value ]];
							})
						};
					});
					// row headings = x axis labels
					xaxis = {
						tickColor: '#EFEDEE',
						labelWidth: 10,
						autoscaleMargin: 0.02, // allow space left and right
						ticks: $.map( $( 'tbody th', table ), function( th, row ) {
							return [[ row, $( th ).text() ]];
						})
					};
				}
				series = {
					lines: {
						show: true,
						lineWidth: 1
					},
					points: { show: false }
				};
				legend = { show: !! config.legend };
				if ( typeof config.legend === 'string' && $( config.legend ).length > 0 ) {
					legend.container = config.legend;
				}
				break;

			case 'bar':
				// grouping data
				if ( config.group !== 'row' ) {
					// [ a, b, c ] => { label: a, data: [[ x1, b ], [ x2, c ]] }
					data = $.map( data, function( d, row ) {
						return {
							label: d.shift(),
							data: $.map( d, function( value, col ) {
								if ( value > yaxis.max ) {
									yaxis.max = value;
								}
								return [[ row + ( col * ( rows + 1 )), value ]];
							})
						};
					});
					// col headings = x axis labels
					xaxis = {
						tickColor: '#EFEDEE',
						autoscaleMargin: 0.02, // allow space left and right
						ticks: $.map( $( 'thead th', table ).slice( 1 ), function( th, col ) {
							return [[ Math.floor( rows / 2 ) + ( col * ( rows + 1 )), $( th ).text() ]];
						})
					};

				} else {
					// group by row
					// row 1 = [ th, a, b ] -> { label: thead > th, data: [[ x1, a ], [ x2, m ], [ x3, x ]] }
					// row 2 = [ th, m, n ]
					// row 3 = [ th, x, y ]
					// note: assume row headings
					data = $.map( data[ 0 ].slice( 1 ), function( d, col ) {
						return {
							label: $( 'thead th', table ).eq( col + 1 ).text(),
							data: $.map( data, function( rowData, row ) {
								var value = rowData[ col + 1 ];
								if ( value > yaxis.max ) {
									yaxis.max = value;
								}
								return [[ col + ( row * ( cols + 1 )), value ]];
							})
						};
					});
					// row headings = x axis labels
					xaxis = {
						tickColor: '#EFEDEE',
						labelWidth: 10,
						autoscaleMargin: 0.02, // allow space left and right
						ticks: $.map( $( 'tbody th', table ), function( th, row ) {
							return [[ Math.floor( cols / 2 ) + ( row * ( cols + 1 )), $( th ).text() ]];
						})
					};
				}
				series = {
					bars: {
						show: true,
						barWidth: 0.75,
						align: 'center',
						fill: 1.0
					}
				};
				legend = { show: !! config.legend };
				if ( typeof config.legend === 'string' && $( config.legend ).length > 0 ) {
					legend.container = config.legend;
				}
				break;

			case 'pie':
				// assume first cell is headings
				// [ a, b, c ] => { label: a, data: [ b, c ] }
				data = $.map( data, function( d ) {
					return {
						label: d.shift(),
						data: d
					};
				});
				series = {
					pie: {
						show: true,
						radius: 0.75,
						label: {
							show: true,
							radius: 0.85,
							formatter: function( label, series ) {
								return Math.round( series.percent ) + '%';
							},
							background: { color: 'transparent' }
						}
					}
				};

				grid = {
					hoverable: true,
					clickable: true
				};

				if ( !! config.legend ) {
					legend = {
						show: true,
						// include % value in legend labels
						labelFormatter: function( label, series ) {
							if ( series.percent === 0 ) {
								return null;
							}
							return label + ' (' + Math.round( series.percent ) + '%)';
						},
						backgroundColor: 'transparent'
					};
				} else {
					legend = { show: false };
				}
				break;
			}

			// add margin to yaxis
			yaxis.max += yaxis.max / 10;

			// console.log( 'chart data', data, xaxis, yaxis );
			// create chart
			$.plot( chart, data, {
				series: series,
				grid: grid,
				colors: config.colours,
				xaxis: xaxis,
				yaxis: yaxis,
				legend: legend
			});

			// check pie labels for overlap
			if ( config.type === 'pie' ) {
				(function() {
					var labels = $( '.pieLabel', chart ), label, prevLabel;
					if ( labels.length > 1 ) {
						for ( var i = 0; i < labels.length; i++ ) {
							label = labels.eq( i );
							prevLabel = labels.eq( i - 1 );
							if ( ! (
								// bounding box collision detection
								// http://devmag.org.za/2009/04/13/basic-collision-detection-in-2d-part-1/
								label.offset().top + label.height() < prevLabel.offset().top ||
								label.offset().top > prevLabel.offset().top + prevLabel.height() ||
								label.offset().left > prevLabel.offset().left + prevLabel.width() ||
								label.offset().left + label.width() < prevLabel.offset().left
							)) {
								// move this label above the previous one
								label.css( 'top', prevLabel.position().top - ( label.height() + 1 ));
							}
						}
					} else {
						// 1 label = 100%
						labels.addClass( 'pieLabel100' );
					}
				}());
			}

			// trigger SWE layout reflow
			container.trigger( 'x-height-change' );
		};

		// charts
		callback.pie = callback.chart;
		callback.line = callback.chart;
		callback.bar = callback.chart;

		// default callback type
		if ( typeof callback[ config.type ] !== 'function' ) {
			config.type = 'ul';
		}

		// get data from CKAN
		qg.data.get( 'data.qld.gov.au', config.sql, callback[ config.type ] );
	});

}( jQuery, qg ));
<commit_msg>fix width of bars<commit_after>/*globals qg*/
(function( $, qg ) {
	'use strict';

	// assume: qg.swe, qg.data.get

	// add data to a page as text, a list or chart
	qg.swe.loaded( 'data', function( config ) {
		config = $.extend({
			title: 'Data',
			headings: [],
			rowHeadings: true,
			group: 'col', // note: anything other than 'row' becomes col anyway
			formats: {},
			type: 'bar',
			legend: true,
			supplementary: [],
			colours: [ '#313695', '#4575B4', '#74ADD1', '#ABD9E9', '#FEE090', '#FDAE61', '#F46D43', '#D73027', '#A50026' ],
			delimiter: ', '
		}, config );

		var callback = {};

		// colour keywords
		if ( typeof config.colours === 'string' ) {
			switch ( config.colours.toLowerCase() ) {
			case 'rag':
				config.colours = [ '#d80000', '#ffb400', '#007c00' ];
				break;

			case 'priority':
				config.colours = [ '#F46D43', '#FDAE61', '#313695', '#ABD9E9' ];
				break;

			case 'initiatives':
				config.colours = [ '#313695' ];
				break;
			}
		}

		callback.text = function( data ) {
			var container = $( document.getElementById( config.container ));
			// check container exists
			if ( container.length === 0 ) {
				$.debug( 'Element not found for data', config.container );
				return;
			}

			// add data to page
			if ( data.result.records.length > 0 ) {
				container.html(
					$.map( data.result.records, function( record ) {
						record = $.map( record, function( value, key ) {
							return qg.swe.template.format( value, config.formats[ key ] );
						}).join( config.delimiter );
						return record;
					}).join( '\n' )
				);
			}
			// trigger SWE layout reflow
			container.trigger( 'x-height-change' );
		};

		callback.ul = function( data ) {
			var container = $( document.getElementById( config.container ));

			// check container exists
			if ( container.length === 0 ) {
				$.debug( 'Element not found for data', config.container );
				return;
			}

			// add data to page
			if ( data.result.records.length > 0 ) {
				container.html(
					'
' +
						$.map( data.result.records, function( record ) {
							record = $.map( record, function( value, key ) {
								return qg.swe.template.format( value, config.formats[ key ] );
							}).join( config.delimiter );
							return '
' + qg.swe.template.clean( record ) + '
';
						}).join( '\n' ) +
					'
'
				);
			}
			// trigger SWE layout reflow
			container.trigger( 'x-height-change' );
		};

		callback.dl = function( data ) {
			var container = $( document.getElementById( config.container ));

			// check container exists
			if ( container.length === 0 ) {
				$.debug( 'Element not found for chart', config.container );
				return;
			}

			// add data table to page
			var hideClass = '',
			    rowCount = 0
			;

			container.html( '
Displaying ' + data.result.records.length + ' search results
' +
				$.map( data.result.records, function( rowData, dlHeading ) {
					rowCount++;
					rowData = $.map( config.headings, function( key, i ) {

						hideClass = $.inArray( key, config.supplementary ) > -1 ?  ' class="supplementary"' : '';
						if (i !== 0 ) {
							return '
' + config.headings[ i ] + '

' + qg.swe.template.format( rowData[ key ], config.formats[ key ], { abbr: 'both' } ) + '
';
						} else {
							dlHeading = '
' + qg.swe.template.format( rowData[ key ], config.formats[ key ], { abbr: 'both' } ) + '
';
						}
					});
					return '
' + dlHeading + '
' + rowData.join( '' ) + '

' +
					       ( config.supplementary.length > 0 ? 'Show more…' : '' );
				}).join( '' )
			);

			// setup lightbox
			$( 'a.more', container ).butterfly();

			// trigger SWE layout reflow
			container.trigger( 'x-height-change' );
		};

		callback.chart = function( data ) {
			var container = $( document.getElementById( config.container )),
				even = true,
				table, w, h, rows, cols,
				chart, series, grid, xaxis, yaxis, legend
			;

			// check container exists
			if ( container.length === 0 ) {
				$.debug( 'Element not found for chart', config.container );
				return;
			}

			// add data table to page
			container.html(
				'
' +
					'
' + qg.swe.template.clean( config.title ) + '
' +
					'
' + $.map( config.headings, function( h ) { return '' + qg.swe.template.clean( h ) + '	'; } ).join( '' ) + '
' +
					'
' +
					$.map( data.result.records, function( rowData ) {
						var tagName;
						rowData = $.map( config.headings.slice( 0 ), function( key, i ) {
							tagName = config.rowHeadings && i === 0 ? 'th' : 'td';
							return '<' + tagName + ( tagName === 'th' ? ' scope="row"' : '' ) + '>' +
								   qg.swe.template.format( rowData[ key ], config.formats[ key ] ) + '</' + tagName + '>';
						});
						return '
' + rowData.join( '' ) + '
';
					}).join( '' ) +
					'
' +
				'
'
			);

			// put table into a tab section
			table = container.find( 'table' );
			table.wrap( '
' ).before( '
Table
' );
			table.parent().generateId( config.container + '-table' );
			// get height (this won't change inside tabs)
			// min height of 250px (pie charts broken in FF at 140px height)
			h = Math.max( table.height(), 250 );
			// don't get width here, padding inside tabs will reduce it

			// chart section
			chart = $( '

Chart

' )
				.generateId( config.container + '-chart' )
				.prependTo( container )
			;
			// chart container (within chart section)
			chart = $( '
' ).appendTo( chart );

			// initialise tabs (this is so chart size will be correct)
			container.compact({
				type: 'tabbed',
				variableHeight: true,
				rememberStateInCookie: false
			});

			// get width within tabs
			w = chart.width();
			// set height (required for flot)
			chart.height( h );
			rows = $( 'tbody tr', table ).length;
			cols = $( 'tbody tr', table ).eq( 0 ).find( 'td' ).length; // don't count th

			// TODO make this a plugin that can run on any existing HTML table

			// prep data for chart
			xaxis = {};
			yaxis = {
				tickDecimals: 0
			};

			// this code works for:
			// - column headings = legend
			// - row headings = xaxis (groups of bars)
			data = $.map( $( 'tbody tr', table ), function( tr ) {
				return [ $.map( $( 'th, td', tr ), function( td ) {
					td = $( td );
					var text = td.text();
					if ( td.is( 'td' )) {
						text = parseFloat( text.replace( /[^0-9\.]+/g, '' ));
					}
					return text;
				})];
			});
			// console.log( 'table data', data );

			switch ( config.type ) {
			case 'line':
			case 'bar':
				// grouping data
				if ( config.group !== 'row' ) {
					// [ a, b, c ] => { label: a, data: [[ x1, b ], [ x2, c ]] }
					data = $.map( data, function( d, row ) {
						return {
							label: d.shift(),
							bars: { order: row },
							data: $.map( d, function( value, col ) {
								return [[ col, value ]];
							})
						};
					});
					// col headings = x axis labels
					xaxis = {
						ticks: $.map( $( 'thead th', table ).slice( 1 ), function( th, col ) {
							return [[ col, $( th ).text() ]];
						})
					};

				} else {
					// group by row
					// row 1 = [ th, a, b ] -> { label: thead > th, data: [[ x1, a ], [ x2, m ], [ x3, x ]] }
					// row 2 = [ th, m, n ]
					// row 3 = [ th, x, y ]
					// note: assume row headings
					data = $.map( data[ 0 ].slice( 1 ), function( d, col ) {
						return {
							label: $( 'thead th', table ).eq( col + 1 ).text(),
							bars: { order: col },
							data: $.map( data, function( rowData, row ) {
								var value = rowData[ col + 1 ];
								return [[ row, value ]];
							})
						};
					});
					// row headings = x axis labels
					xaxis = {
						ticks: $.map( $( 'tbody th', table ), function( th, row ) {
							return [[ row, $( th ).text() ]];
						})
					};
				}
				if ( config.type === 'line' ) {
					series = {
						lines: {
							show: true,
							lineWidth: 1
						},
						points: { show: false },
						shadowSize: 0
					};
				} else {
					// assume bar chart
					series = {
						bars: {
							show: true,
							// lineWidth: 0,
							barWidth: 0.5 / data.length,
							align: 'center',
							fill: 1.0
						}
					};
				}
				xaxis.tickColor = '#EFEDEE';
				xaxis.autoscaleMargin = 0.5 / data.length / data[ 0 ].data.length;
				legend = { show: !! config.legend };
				if ( typeof config.legend === 'string' && $( config.legend ).length > 0 ) {
					legend.container = config.legend;
				}
				break;

			case 'pie':
				// assume first cell is headings
				// [ a, b, c ] => { label: a, data: [ b, c ] }
				data = $.map( data, function( d ) {
					return {
						label: d.shift(),
						data: d
					};
				});
				series = {
					pie: {
						show: true,
						radius: 0.75,
						label: {
							show: true,
							radius: 0.85,
							formatter: function( label, series ) {
								return Math.round( series.percent ) + '%';
							},
							background: { color: 'transparent' }
						}
					}
				};

				grid = {
					hoverable: true,
					clickable: true
				};

				if ( !! config.legend ) {
					legend = {
						show: true,
						// include % value in legend labels
						labelFormatter: function( label, series ) {
							if ( series.percent === 0 ) {
								return null;
							}
							return label + ' (' + Math.round( series.percent ) + '%)';
						},
						backgroundColor: 'transparent'
					};
				} else {
					legend = { show: false };
				}
				break;
			}

			// create chart
			$.plot( chart, data, {
				series: series,
				grid: grid,
				colors: config.colours,
				xaxis: xaxis,
				yaxis: yaxis,
				legend: legend
			});

			// check pie labels for overlap
			if ( config.type === 'pie' ) {
				(function() {
					var labels = $( '.pieLabel', chart ), label, prevLabel;
					if ( labels.length > 1 ) {
						for ( var i = 0; i < labels.length; i++ ) {
							label = labels.eq( i );
							prevLabel = labels.eq( i - 1 );
							if ( ! (
								// bounding box collision detection
								// http://devmag.org.za/2009/04/13/basic-collision-detection-in-2d-part-1/
								label.offset().top + label.height() < prevLabel.offset().top ||
								label.offset().top > prevLabel.offset().top + prevLabel.height() ||
								label.offset().left > prevLabel.offset().left + prevLabel.width() ||
								label.offset().left + label.width() < prevLabel.offset().left
							)) {
								// move this label above the previous one
								label.css( 'top', prevLabel.position().top - ( label.height() + 1 ));
							}
						}
					} else {
						// 1 label = 100%
						labels.addClass( 'pieLabel100' );
					}
				}());
			}

			// trigger SWE layout reflow
			container.trigger( 'x-height-change' );
		};

		// charts
		callback.pie = callback.chart;
		callback.line = callback.chart;
		callback.bar = callback.chart;

		// default callback type
		if ( typeof callback[ config.type ] !== 'function' ) {
			config.type = 'ul';
		}

		// get data from CKAN
		qg.data.get( 'data.qld.gov.au', config.sql, callback[ config.type ] );
	});

}( jQuery, qg ));
<|endoftext|>


<commit_before>//
'use strict';

// Extend objects
module.exports.extend = function (object, properties) {
	var val;
	for (var key in properties) {
		if (properties.hasOwnProperty(key)) {
			val = properties[key];
			object[key] = val;
		}
	}
	return object;
};

// Concatenate URLs
module.exports.genUrl = function (opt) {
	var _url = (opt.prot || 'https') + '://' + opt.host;

	if (opt.port) {
		_url += ':' + opt.port;
	}
	_url += opt.path;
	return _url;
};
<commit_msg>Extract response parsing to util<commit_after>//
'use strict';

module.exports.resParse = function (err, res, data) {
	if (err) throw new Error(err);

	try {
		data = JSON.parse(data);
		if (res.statusCode > 400) {
			return [new Error(res.statusMessage, res.statusCode), data];
		} else {
			return [null, data];
		}
	} catch (err) {
		return [err, data];
	}
};

// Extend objects
module.exports.extend = function (object, properties) {
	var val;
	for (var key in properties) {
		if (properties.hasOwnProperty(key)) {
			val = properties[key];
			object[key] = val;
		}
	}
	return object;
};

// Concatenate URLs
module.exports.genUrl = function (opt) {
	var _url = (opt.prot || 'https') + '://' + opt.host;

	if (opt.port) {
		_url += ':' + opt.port;
	}
	_url += opt.path;
	return _url;
};
<|endoftext|>


<commit_before>module.exports = {
  extract: function (obj /* keys */) {
    var keys = [].slice.call(arguments, 1),
        collected = {};

    keys.forEach(function (key) {
      if (obj[key]) {
        collected[key] = obj[key];
      }
    });

    return collected;
  },
  shortcode: function () {
    var vowels = 'aeiou',
        consonants = 'bcdfghjklmnpqrstvwxyz',
        word = '', length = 6, index = 0, set;

    for (; index < length; index += 1) {
      set = (index % 2 === 0) ? vowels : consonants;
      word += set[Math.floor(Math.random() * set.length)];
    }

    return word;
  },
  titleForBin: function (bin) {
    // Try and get title from HTML first, unlike the PHP version we're
    // returning the HTML title first as that seems to make most sense.
    // TODO: Use HTML parser or JSDOM
    var matches = bin.html.match(/<title>([^>]*)<\/title>/),
        javascript;

    if (matches) {
      return matches[1];
    }

    // No title return JavaScript.
    javascript = bin.javascript.trim();
    if (javascript) {
      return javascript.replace(/\s+/g, ' ');
    }

    matches = bin.html.match(/<body.*>([^\s]*)/);
    if (matches) {
      return matches[1];
    }
    return '';
  }
};
<commit_msg>Fix issue with utils.titleFromBin() where there is no CSS<commit_after>module.exports = {
  extract: function (obj /* keys */) {
    var keys = [].slice.call(arguments, 1),
        collected = {};

    keys.forEach(function (key) {
      if (obj[key]) {
        collected[key] = obj[key];
      }
    });

    return collected;
  },
  shortcode: function () {
    var vowels = 'aeiou',
        consonants = 'bcdfghjklmnpqrstvwxyz',
        word = '', length = 6, index = 0, set;

    for (; index < length; index += 1) {
      set = (index % 2 === 0) ? vowels : consonants;
      word += set[Math.floor(Math.random() * set.length)];
    }

    return word;
  },
  titleForBin: function (bin) {
    // Try and get title from HTML first, unlike the PHP version we're
    // returning the HTML title first as that seems to make most sense.
    // TODO: Use HTML parser or JSDOM
    var html = bin.html || '',
        javascript = (bin.javascript || '').trim(),
        matches = html.match(/<title>([^>]*)<\/title>/);

    if (matches) {
      return matches[1];
    }

    // No title return JavaScript.
    if (javascript) {
      return javascript.replace(/\s+/g, ' ');
    }

    matches = html.match(/<body.*>([^\s]*)/);
    if (matches) {
      return matches[1];
    }
    return '';
  }
};
<|endoftext|>


<commit_before>
if (typeof String.prototype.endsWith !== 'function') {
  String.prototype.endsWith = function (str){
    return this.slice(-str.length) === str
  }
}

if (typeof String.prototype.startsWith !== 'function') {
  String.prototype.startsWith = function (str, index) {
    index = index || 0
    return this.slice(index, index + str.length) === str
  }
}

module.exports = {
  isAzStyle: function (peerId) {
    if (peerId.charAt(0) !== '-') return false
    if (peerId.charAt(7) === '-') return true

    /**
     * Hack for FlashGet - it doesn't use the trailing dash.
     * Also, LH-ABC has strayed into "forgetting about the delimiter" territory.
     *
     * In fact, the code to generate a peer ID for LH-ABC is based on BitTornado's,
     * yet tries to give an Az style peer ID... oh dear.
     *
     * BT Next Evolution seems to be in the same boat as well.
     *
     * KTorrent 3 appears to use a dash rather than a final character.
     */
    if (peerId.substring(1, 3) === "FG") return true
    if (peerId.substring(1, 3) === "LH") return true
    if (peerId.substring(1, 3) === "NE") return true
    if (peerId.substring(1, 3) === "KT") return true
    if (peerId.substring(1, 3) === "SP") return true

    return false
  },

  /**
   * Checking whether a peer ID is Shadow style or not is a bit tricky.
   *
   * The BitTornado peer ID convention code is explained here:
   *   http://forums.degreez.net/viewtopic.php?t=7070
   *
   * The main thing we are interested in is the first six characters.
   * Although the other characters are base64 characters, there's no
   * guarantee that other clients which follow that style will follow
   * that convention (though the fact that some of these clients use
   * BitTornado in the core does blur the lines a bit between what is
   * "style" and what is just common across clients).
   *
   * So if we base it on the version number information, there's another
   * problem - there isn't the use of absolute delimiters (no fixed dash
   * character, for example).
   *
   * There are various things we can do to determine how likely the peer
   * ID is to be of that style, but for now, I'll keep it to a relatively
   * simple check.
   *
   * We'll assume that no client uses the fifth version digit, so we'll
   * expect a dash. We'll also assume that no client has reached version 10
   * yet, so we expect the first two characters to be "letter,digit".
   *
   * We've seen some clients which don't appear to contain any version
   * information, so we need to allow for that.
   */
  isShadowStyle: function(peerId) {
    if (peerId.charAt(5) !== '-') return false
    if (!isLetter(peerId.charAt(0))) return false
    if (!(isDigit(peerId.charAt(1)) || peerId.charAt(1) === '-')) return false

    // Find where the version number string ends.
    var lastVersionNumberIndex = 4
    for (; lastVersionNumberIndex > 0; lastVersionNumberIndex--) {
      if (peerId.charAt(lastVersionNumberIndex) !== '-') break
    }

    // For each digit in the version string, check if it is a valid version identifier.
    for (var i = 1; i <= lastVersionNumberIndex; i++) {
      var c = peerId.charAt(i)
      if (c === '-') return false
      if (isAlphaNumeric(c) === null) return false
    }

    return true
  },

  isMainlineStyle: function (peerId) {
    /**
     * One of the following styles will be used:
     *   Mx-y-z--
     *   Mx-yy-z-
     */
    return peerId.charAt(2) === '-' && peerId.charAt(7) === '-' &&
      (peerId.charAt(4) === '-' || peerId.charAt(5) === '-')
  },

  isPossibleSpoofClient: function (peerId) {
    return peerId.endsWith('UDP0') || peerId.endsWith('HTTPBT')
  },

  decodeNumericValueOfByte: decodeNumericValueOfByte,

  getAzStyleVersionNumber: function (peerId, version) {
    // TODO
    return null
  },

  getShadowStyleVersionNumber: function (peerId) {
    // TODO
    return null
  },

  decodeBitSpiritClient: function (peerId, buffer) {
    if (peerId.substring(2, 4) !== 'BS') return null
    var version = '' + buffer[1]
    if (version === '0') version = 1

    return {
      client: "BitSpirit",
      version: version
    }
  },

  decodeBitCometClient: function (peerId, buffer) {
    var modName = ""
    if (peerId.startsWith("exbc")) modName = ""
    else if (peerId.startsWith("FUTB")) modName = "(Solidox Mod)"
    else if (peerId.startsWith("xUTB")) modName = "(Mod 2)"
    else return null

    var isBitlord = (peerId.substring(6, 10) === "LORD")

    // Older versions of BitLord are of the form x.yy, whereas new versions (1 and onwards),
    // are of the form x.y. BitComet is of the form x.yy
    var clientName = (isBitlord) ? "BitLord" : "BitComet"
    var majVersion = decodeNumericValueOfByte(buffer[4])
    var minVersionLength = (isBitlord && majVersion !== "0" ? 1 : 2)

    return {
      client: clientName + (modName ? " " + modName : ""),
      version: majVersion + "." + decodeNumericValueOfByte(buffer[5], minVersionLength)
    }
  },

  identifyAwkwardClient: function (peerId, buffer) {
    var firstNonZeroIndex = 20
    var i

    for (i = 0; i < 20; ++i) {
      if (buffer[i] > 0) {
        firstNonZeroIndex = i
        break
      }
    }

    // Shareaza check
    if (firstNonZeroIndex === 0) {
      var isShareaza = true
      for (i = 0; i < 16; ++i) {
        if (buffer[i] === 0) {
          isShareaza = false
          break
        }
      }

      if (isShareaza) {
        for (i = 16; i < 20; ++i) {
          if (buffer[i] !== (buffer[i % 16] ^ buffer[15 - (i % 16)])) {
            isShareaza = false
            break
          }
        }

        if (isShareaza) return { client: "Shareaza" }
      }
    }

    if (firstNonZeroIndex === 9 && buffer[9] === 3 && buffer[10] === 3 && buffer[11] === 3)
      return { client: "I2PSnark" }

    if (firstNonZeroIndex === 12 && buffer[12] === 97 && buffer[13] === 97)
      return { client: "Experimental", version: "3.2.1b2" }

    if (firstNonZeroIndex === 12 && buffer[12] === 0 && buffer[13] === 0)
      return { client: "Experimental", version: "3.1" }

    if (firstNonZeroIndex === 12)
      return { client: "Mainline" }

    return null
  }
}

//
// Private helper functions for the public utility functions
//

function isDigit (s) {
  var code = s.charCodeAt(0)
  return code >= '0'.charCodeAt(0) && code <= '9'.charCodeAt(0)
}

function isLetter (s) {
  var code = s.toLowerCase().charCodeAt(0)
  return code >= 'a'.charCodeAt(0) && code <= 'z'.charCodeAt(0)
}

function isAlphaNumeric (s) {
  return isDigit(s) || isLetter(s) || s === '.'
}

function decodeNumericValueOfByte (b, minDigits) {
  minDigits = minDigits || 0
  var result = '' + (b & 0xff)
  while (result.length < minDigits) { result = '0' + result }
  return result
}

<commit_msg>If version is function run it<commit_after>
if (typeof String.prototype.endsWith !== 'function') {
  String.prototype.endsWith = function (str){
    return this.slice(-str.length) === str
  }
}

if (typeof String.prototype.startsWith !== 'function') {
  String.prototype.startsWith = function (str, index) {
    index = index || 0
    return this.slice(index, index + str.length) === str
  }
}

module.exports = {
  isAzStyle: function (peerId) {
    if (peerId.charAt(0) !== '-') return false
    if (peerId.charAt(7) === '-') return true

    /**
     * Hack for FlashGet - it doesn't use the trailing dash.
     * Also, LH-ABC has strayed into "forgetting about the delimiter" territory.
     *
     * In fact, the code to generate a peer ID for LH-ABC is based on BitTornado's,
     * yet tries to give an Az style peer ID... oh dear.
     *
     * BT Next Evolution seems to be in the same boat as well.
     *
     * KTorrent 3 appears to use a dash rather than a final character.
     */
    if (peerId.substring(1, 3) === "FG") return true
    if (peerId.substring(1, 3) === "LH") return true
    if (peerId.substring(1, 3) === "NE") return true
    if (peerId.substring(1, 3) === "KT") return true
    if (peerId.substring(1, 3) === "SP") return true

    return false
  },

  /**
   * Checking whether a peer ID is Shadow style or not is a bit tricky.
   *
   * The BitTornado peer ID convention code is explained here:
   *   http://forums.degreez.net/viewtopic.php?t=7070
   *
   * The main thing we are interested in is the first six characters.
   * Although the other characters are base64 characters, there's no
   * guarantee that other clients which follow that style will follow
   * that convention (though the fact that some of these clients use
   * BitTornado in the core does blur the lines a bit between what is
   * "style" and what is just common across clients).
   *
   * So if we base it on the version number information, there's another
   * problem - there isn't the use of absolute delimiters (no fixed dash
   * character, for example).
   *
   * There are various things we can do to determine how likely the peer
   * ID is to be of that style, but for now, I'll keep it to a relatively
   * simple check.
   *
   * We'll assume that no client uses the fifth version digit, so we'll
   * expect a dash. We'll also assume that no client has reached version 10
   * yet, so we expect the first two characters to be "letter,digit".
   *
   * We've seen some clients which don't appear to contain any version
   * information, so we need to allow for that.
   */
  isShadowStyle: function(peerId) {
    if (peerId.charAt(5) !== '-') return false
    if (!isLetter(peerId.charAt(0))) return false
    if (!(isDigit(peerId.charAt(1)) || peerId.charAt(1) === '-')) return false

    // Find where the version number string ends.
    var lastVersionNumberIndex = 4
    for (; lastVersionNumberIndex > 0; lastVersionNumberIndex--) {
      if (peerId.charAt(lastVersionNumberIndex) !== '-') break
    }

    // For each digit in the version string, check if it is a valid version identifier.
    for (var i = 1; i <= lastVersionNumberIndex; i++) {
      var c = peerId.charAt(i)
      if (c === '-') return false
      if (isAlphaNumeric(c) === null) return false
    }

    return true
  },

  isMainlineStyle: function (peerId) {
    /**
     * One of the following styles will be used:
     *   Mx-y-z--
     *   Mx-yy-z-
     */
    return peerId.charAt(2) === '-' && peerId.charAt(7) === '-' &&
      (peerId.charAt(4) === '-' || peerId.charAt(5) === '-')
  },

  isPossibleSpoofClient: function (peerId) {
    return peerId.endsWith('UDP0') || peerId.endsWith('HTTPBT')
  },

  decodeNumericValueOfByte: decodeNumericValueOfByte,

  getAzStyleVersionNumber: function (peerId, version) {
	if ( typeof version == 'function') {
		return version(peerId)
	}
    return null
  },

  getShadowStyleVersionNumber: function (peerId) {
    // TODO
    return null
  },

  decodeBitSpiritClient: function (peerId, buffer) {
    if (peerId.substring(2, 4) !== 'BS') return null
    var version = '' + buffer[1]
    if (version === '0') version = 1

    return {
      client: "BitSpirit",
      version: version
    }
  },

  decodeBitCometClient: function (peerId, buffer) {
    var modName = ""
    if (peerId.startsWith("exbc")) modName = ""
    else if (peerId.startsWith("FUTB")) modName = "(Solidox Mod)"
    else if (peerId.startsWith("xUTB")) modName = "(Mod 2)"
    else return null

    var isBitlord = (peerId.substring(6, 10) === "LORD")

    // Older versions of BitLord are of the form x.yy, whereas new versions (1 and onwards),
    // are of the form x.y. BitComet is of the form x.yy
    var clientName = (isBitlord) ? "BitLord" : "BitComet"
    var majVersion = decodeNumericValueOfByte(buffer[4])
    var minVersionLength = (isBitlord && majVersion !== "0" ? 1 : 2)

    return {
      client: clientName + (modName ? " " + modName : ""),
      version: majVersion + "." + decodeNumericValueOfByte(buffer[5], minVersionLength)
    }
  },

  identifyAwkwardClient: function (peerId, buffer) {
    var firstNonZeroIndex = 20
    var i

    for (i = 0; i < 20; ++i) {
      if (buffer[i] > 0) {
        firstNonZeroIndex = i
        break
      }
    }

    // Shareaza check
    if (firstNonZeroIndex === 0) {
      var isShareaza = true
      for (i = 0; i < 16; ++i) {
        if (buffer[i] === 0) {
          isShareaza = false
          break
        }
      }

      if (isShareaza) {
        for (i = 16; i < 20; ++i) {
          if (buffer[i] !== (buffer[i % 16] ^ buffer[15 - (i % 16)])) {
            isShareaza = false
            break
          }
        }

        if (isShareaza) return { client: "Shareaza" }
      }
    }

    if (firstNonZeroIndex === 9 && buffer[9] === 3 && buffer[10] === 3 && buffer[11] === 3)
      return { client: "I2PSnark" }

    if (firstNonZeroIndex === 12 && buffer[12] === 97 && buffer[13] === 97)
      return { client: "Experimental", version: "3.2.1b2" }

    if (firstNonZeroIndex === 12 && buffer[12] === 0 && buffer[13] === 0)
      return { client: "Experimental", version: "3.1" }

    if (firstNonZeroIndex === 12)
      return { client: "Mainline" }

    return null
  }
}

//
// Private helper functions for the public utility functions
//

function isDigit (s) {
  var code = s.charCodeAt(0)
  return code >= '0'.charCodeAt(0) && code <= '9'.charCodeAt(0)
}

function isLetter (s) {
  var code = s.toLowerCase().charCodeAt(0)
  return code >= 'a'.charCodeAt(0) && code <= 'z'.charCodeAt(0)
}

function isAlphaNumeric (s) {
  return isDigit(s) || isLetter(s) || s === '.'
}

function decodeNumericValueOfByte (b, minDigits) {
  minDigits = minDigits || 0
  var result = '' + (b & 0xff)
  while (result.length < minDigits) { result = '0' + result }
  return result
}

<|endoftext|>


<commit_before>var protocol = require('./protocol');
var ipaddr = require('ipaddr.js');
var protocol = require('./protocol');
var queryTypes = protocol.queryTypes

function Query(arg) {
        if (typeof(arg) !== 'object')
                throw new TypeError('arg (object) is missing');

        var self = this;

        this.id = arg.id;
        this._truncated = false;
        this._authoritative = [];  // set on response
        this._recursionAvailable = false; // set on response
        this._responseCode = 0;
        this._qdCount = arg.qdCount;
        this._anCount = arg.anCount || 0;
        this._nsCount = arg.nsCount || 0;
        this._arCount = arg.arCount || 0;
        this._flags = arg.flags;
        this._question = arg.question;
        this._answers = [];
        this._additional = [];
        this._raw = null;
        this._client = null;
}

Query.prototype.answers = function answers() {
        return this._answers.map(function(r) {
                return {
                        name: r.name,
                        type: queryTypes[r.rtype],
                        record: r.rdata,
                        ttl: r.rttl
                };
        });
}

Query.prototype.name = function name() {
        return this._question.name;
}

Query.prototype.type = function type() {
        return queryTypes[this._question.type];
}

Query.prototype.operation = function operation() {
        switch (this._flags.opcode) {
        case 0:
                return 'query';
        case 2:
                return 'status';
        case 4:
                return 'notify';
        case 5:
                return 'update';
        default:
                throw new Error('invalid operation %d', this._flags.opcode);
        }
};

Query.prototype.encode = function encode() {
        var header, question, answer, rSize, rBuffer;

        // TODO get rid of this intermediate format (or justify it)
        var toPack = {
                header: {
                        id: this.id,
                        flags: this._flags,
                        qdCount: this._qdCount,
                        anCount: this._anCount,
                        nsCount: this._nsCount,
                        arCount: this._arCount
                },
                question: (this._qdCount > 0) ? this._question : undefined,
                answers: this._answers,
                authority: this._authoritative,
                additional: this._additional
        }
        var encoded = protocol.encode(toPack, (this._qdCount > 0) ? 'answerMessage' : 'answerMessageNoQ');

        this._raw = {
                buf: encoded,
                len: encoded.length
        };
};
Query.prototype.addAuthority = function(name, record, ttl) {
        if (typeof (name) !== 'string')
                throw new TypeError('name (string) required');
        if (typeof (record) !== 'object')
                throw new TypeError('record (Record) required');
        if (ttl !== undefined && typeof (ttl) !== 'number')
                throw new TypeError('ttl (number) required');

        var authority = {
                name:   name,
                rtype:  queryTypes['NS'],
                rclass: 1,  // INET
                rttl:   ttl || 5,
                rdata:  record
        };

        this._authoritative.push(authority);
        this._nsCount++;
        this._flags.aa = true;
};
Query.prototype.addAdditional = function(name, record, ttl) {
        if (typeof (name) !== 'string')
                throw new TypeError('name (string) required');
        if (typeof (record) !== 'object')
                throw new TypeError('record (Record) required');
        if (ttl !== undefined && typeof (ttl) !== 'number')
                throw new TypeError('ttl (number) required');

        var add = {
                name:   name,
                rtype:  queryTypes[record._type],
                rclass: 1,  // INET
                rttl:   ttl || 5,
                rdata:  record
        };

        this._additional.push(add);
        this._arCount++;
};

Query.prototype.addAnswer = function(name, record, ttl) {
        if (typeof (name) !== 'string')
                throw new TypeError('name (string) required');
        if (typeof (record) !== 'object')
                throw new TypeError('record (Record) required');
        if (ttl !== undefined && typeof (ttl) !== 'number')
                throw new TypeError('ttl (number) required');

        if (!queryTypes.hasOwnProperty(record._type))
                throw new Error('unknown queryType: ' + record._type);

        var answer = {
                name:   name,
                rtype:  queryTypes[record._type],
                rclass: 1,  // INET
                rttl:   ttl || 5,
                rdata:  record
        };

        // Note:
        //
        // You can only have multiple answers in certain circumstances in no
        // circumstance can you mix different answer types other than 'A' with
        // 'AAAA' unless they are in the 'additional' section.
        //
        // There are also restrictions on what you can answer with depending on
        // the question.
        //
        // We will not attempt to enforce that here at the moment.
        //

        this._answers.push(answer);
        this._anCount++;
};

function parseQuery(raw, src) {
        var dobj, b = raw.buf;

        dobj = protocol.decode(b, 'queryMessage');

        if (!dobj.val)
                return null;

        // TODO get rid of this intermediate format (or justify it)
        var d = dobj.val;
        var res = {
                id: d.header.id,
                flags: d.header.flags,
                qdCount: d.header.qdCount,
                question: d.question, //XXX
                src: src,
                raw: raw
        }

        return (res);
}

function createQuery(req) {
        var query = new Query(req);
        query._raw = req.raw;
        query._client = req.src;
        return (query);
}

module.exports = {
        createQuery: createQuery,
        parse: parseQuery,
}
<commit_msg>Use response codes for queries<commit_after>var protocol = require('./protocol');
var ipaddr = require('ipaddr.js');
var protocol = require('./protocol');
var queryTypes = protocol.queryTypes

function Query(arg) {
        if (typeof(arg) !== 'object')
                throw new TypeError('arg (object) is missing');

        var self = this;

        this.id = arg.id;
        this._truncated = false;
        this._authoritative = [];  // set on response
        this._recursionAvailable = false; // set on response
        // by default respond with NOTIMP unless we set otherwise
        this._responseCode = protocol.DNS_ENOTIMP;
        this._qdCount = arg.qdCount;
        this._anCount = arg.anCount || 0;
        this._nsCount = arg.nsCount || 0;
        this._arCount = arg.arCount || 0;
        this._flags = arg.flags;
        this._question = arg.question;
        this._answers = [];
        this._additional = [];
        this._raw = null;
        this._client = null;
}

Query.prototype.answers = function answers() {
        return this._answers.map(function(r) {
                return {
                        name: r.name,
                        type: queryTypes[r.rtype],
                        record: r.rdata,
                        ttl: r.rttl
                };
        });
}

Query.prototype.name = function name() {
        return this._question.name;
}

Query.prototype.type = function type() {
        return queryTypes[this._question.type];
}

Query.prototype.setError = function setError(name) {
        var code = protocol['DNS_' + name.toUpperCase()];
        if (code === undefined) {
                throw new Error('invalid error code %s', name);
        }
        this._responseCode = code;
}

Query.prototype.operation = function operation() {
        switch (this._flags.opcode) {
        case 0:
                return 'query';
        case 2:
                return 'status';
        case 4:
                return 'notify';
        case 5:
                return 'update';
        default:
                throw new Error('invalid operation %d', this._flags.opcode);
        }
};

Query.prototype.encode = function encode() {
        var header, question, answer, rSize, rBuffer;

        // TODO get rid of this intermediate format (or justify it)
        this._flags.rcode = this._responseCode;
        var toPack = {
                header: {
                        id: this.id,
                        flags: this._flags,
                        qdCount: this._qdCount,
                        anCount: this._anCount,
                        nsCount: this._nsCount,
                        arCount: this._arCount
                },
                question: (this._qdCount > 0) ? this._question : undefined,
                answers: this._answers,
                authority: this._authoritative,
                additional: this._additional
        }
        var encoded = protocol.encode(toPack, (this._qdCount > 0) ? 'answerMessage' : 'answerMessageNoQ');

        this._raw = {
                buf: encoded,
                len: encoded.length
        };
};
Query.prototype.addAuthority = function(name, record, ttl) {
        if (typeof (name) !== 'string')
                throw new TypeError('name (string) required');
        if (typeof (record) !== 'object')
                throw new TypeError('record (Record) required');
        if (ttl !== undefined && typeof (ttl) !== 'number')
                throw new TypeError('ttl (number) required');

        var authority = {
                name:   name,
                rtype:  queryTypes['NS'],
                rclass: 1,  // INET
                rttl:   ttl || 5,
                rdata:  record
        };

        this._authoritative.push(authority);
        this._nsCount++;
        this._flags.aa = true;
};
Query.prototype.addAdditional = function(name, record, ttl) {
        if (typeof (name) !== 'string')
                throw new TypeError('name (string) required');
        if (typeof (record) !== 'object')
                throw new TypeError('record (Record) required');
        if (ttl !== undefined && typeof (ttl) !== 'number')
                throw new TypeError('ttl (number) required');

        var add = {
                name:   name,
                rtype:  queryTypes[record._type],
                rclass: 1,  // INET
                rttl:   ttl || 5,
                rdata:  record
        };

        this._additional.push(add);
        this._arCount++;
};

Query.prototype.addAnswer = function(name, record, ttl) {
        if (typeof (name) !== 'string')
                throw new TypeError('name (string) required');
        if (typeof (record) !== 'object')
                throw new TypeError('record (Record) required');
        if (ttl !== undefined && typeof (ttl) !== 'number')
                throw new TypeError('ttl (number) required');

        if (!queryTypes.hasOwnProperty(record._type))
                throw new Error('unknown queryType: ' + record._type);

        // return ok, we have an answer
        this._responseCode = protocol.DNS_ENOERR;
        var answer = {
                name:   name,
                rtype:  queryTypes[record._type],
                rclass: 1,  // INET
                rttl:   ttl || 5,
                rdata:  record
        };

        // Note:
        //
        // You can only have multiple answers in certain circumstances in no
        // circumstance can you mix different answer types other than 'A' with
        // 'AAAA' unless they are in the 'additional' section.
        //
        // There are also restrictions on what you can answer with depending on
        // the question.
        //
        // We will not attempt to enforce that here at the moment.
        //

        this._answers.push(answer);
        this._anCount++;
};

function parseQuery(raw, src) {
        var dobj, b = raw.buf;

        dobj = protocol.decode(b, 'queryMessage');

        if (!dobj.val)
                return null;

        // TODO get rid of this intermediate format (or justify it)
        var d = dobj.val;
        var res = {
                id: d.header.id,
                flags: d.header.flags,
                qdCount: d.header.qdCount,
                question: d.question, //XXX
                src: src,
                raw: raw
        }

        return (res);
}

function createQuery(req) {
        var query = new Query(req);
        query._raw = req.raw;
        query._client = req.src;
        return (query);
}

module.exports = {
        createQuery: createQuery,
        parse: parseQuery,
}
<|endoftext|>


<commit_before>"use strict";

/**
 * Format of a todo:
 * * file:  String - Relative path
 * * sha:   String - Commit's sha
 * * line:  Number - File line number
 * * title: String - Issue's title
 * * label: String - Issue's label
 * * issue: Number - (optional) Issue's number
 **/

var path = require("path");
var fs = require("fs");
var async = require("async");
var _ = require("lodash");

var config = require("./config");
var github = require("./github");

module.exports = {
  "fromDiff": fromDiff
};

function createOrCommentIssue (repo, todo, conf, cb) {
  if (todo.issue) {
    commentIssue(todo, conf, cb);
  } else {
    github.findIssueByTitle(repo, todo.title, function (err, issue) {
      if (err) {
        cb(err);
      } else if (issue) {

        todo.issue = issue.number;

        var ops = {
          "comment": _.partial(commentIssue, repo, todo, conf)
        };
        if (!_.contains(issue.labels, todo.label)) {
          ops.tag = _.partial(github.tagIssue, repo, todo.issue, todo.label);
        }

        async.parallel(ops, function (err, res) {
          if (err) {
            return cb(err);
          }

          cb(null, res.comment);
        });

      } else {
        createIssue(repo, todo, conf, cb);
      }
    });
  }
}

function createIssue (repo, todo, conf, cb) {
  console.log("(fake) Creating issue…");
  getCommentText(repo, todo, conf, function (err, text) {
    if (err) {
      return cb(err);
    }

    github.createIssue(repo, todo.title, text, [todo.label], cb);
  });
}

function commentIssue (repo, todo, conf, cb) {
  console.log("(fake) Commenting issue #" + todo.issue + "…");
  getCommentText(repo, todo, conf, function (err, text) {
    if (err) {
      return cb(err);
    }

    github.commentIssue(repo, todo.issue, text, cb);
  });
}

function getCommentText (repo, todo, conf, cb) {
  var text = "";

  // Link to file
  text += "Ref. [" + todo.file + ":" + todo.line + "](" + github.getFileUrl(repo, todo.file, todo.sha, todo.line) + ")";

  // Add code information
  fs.readFile(path.resolve(todo.file), {encoding: "utf8"}, function (err, content) {
    if (err) {
      return cb(err);
    }

    var lines = content.split(/\r\n|\r|\n/);

    // Remove trailing new lines
    while (lines[lines.length - 1] === "") {
      lines.pop();
    }
    while (lines[0] === "") {
      lines.shift();
    }

    if (conf.context > 0) {
      // Extract: line to line + conf.context
      var extract = lines.slice(todo.line - 1, todo.line + conf.context).join("\n");
      if (todo.line + conf.context < lines.length) {
        extract += "\n…";
      }

      // TODO syntax highlighting?
      text += "\n\n```\n" + extract + "\n```\n";
    }

    if (conf.signature) {
      text += "\n" + conf.signature;
    }

    cb(null, text);
  });
}

function fromDiff (repo, diff, sha, conf, cb) {
  if (_.isFunction(conf)) {
    cb = conf;
    conf = null;
  }
  conf = _.merge(conf || {}, config.defaults);

  async.map(_.flatten(_.map(diff, function (file) {
    var addedLines = _.filter(file.lines, "add");
    return _.filter(_.map(addedLines, lineToTodoMapper(file.to, sha, conf)), "title");
  })), function (todo, cb) {
    createOrCommentIssue(repo, todo, conf, cb);
  }, cb);
}

// String, Sha → String → {file, sha, line, title, label}
function lineToTodoMapper (filename, sha, conf) {
  return function lineToTodo (line) {
    return _.merge({
      "file":   filename,
      "sha":    sha,
      "line":   line.ln
    }, extractTodoTitle(line.content, conf));
  };
}

// String → {title, label}
function extractTodoTitle (content, conf) {
  var result = null;

  var labels = {};
  _.each(conf, function (value, key) {
    if (key.match(/^label\./)) {
      var trigger = key.substring(6);
      if (conf["label-whitespace"]) {
        trigger += " ";
      }
      labels[trigger] = value;
    }
  });

  if (_.isString(content)) {
    _.find(Object.keys(labels), function (trigger) {
      var index;
      if (conf["case-sensitive"]) {
        index = content.indexOf(trigger);
      } else {
        index = content.toUpperCase().indexOf(trigger.toUpperCase());
      }

      if (index !== -1) {
        var title = content.substring(index + trigger.length).trim();
        if (title && !isCode(title)) {
          result = {
            "title":  title,
            "label":  labels[trigger]
          };
        }
        return true; // break
      }
    });
  }

  return result;
}

// TODO Better heuristic for code vs words detection

// Simple heuristic to detect if a title is really a title or some valid code
// String → Boolean
function isCode (string) {
  // If symbols are more than 20% of the code, it may be code more than human text
  var symbols = _.filter(string, isSymbol);

  return symbols.length / string.length > 0.20;
}

var RE_SYMBOL = /[^\sa-z0-9\u00E0-\u00FC]/i;
// Matches a symbol: non alphanumeric character
// Character → Boolean
function isSymbol (character) {
  return RE_SYMBOL.test(character);
}
<commit_msg>Support option 'onProgress' to follow results in todos.fromDiff<commit_after>"use strict";

/**
 * Format of a todo:
 * * file:  String - Relative path
 * * sha:   String - Commit's sha
 * * line:  Number - File line number
 * * title: String - Issue's title
 * * label: String - Issue's label
 * * issue: Number - (optional) Issue's number
 **/

var path = require("path");
var fs = require("fs");
var async = require("async");
var _ = require("lodash");

var config = require("./config");
var github = require("./github");

module.exports = {
  "fromDiff": fromDiff
};

function createOrCommentIssue (repo, todo, conf, cb) {
  if (todo.issue) {
    commentIssue(todo, conf, cb);
  } else {
    github.findIssueByTitle(repo, todo.title, function (err, issue) {
      if (err) {
        cb(err);
      } else if (issue) {

        todo.issue = issue.number;

        var ops = {
          "comment": _.partial(commentIssue, repo, todo, conf)
        };
        if (!_.contains(issue.labels, todo.label)) {
          ops.tag = _.partial(github.tagIssue, repo, todo.issue, todo.label);
        }

        async.parallel(ops, function (err, res) {
          if (err) {
            return cb(err);
          }

          cb(null, res.comment);
        });

      } else {
        createIssue(repo, todo, conf, cb);
      }
    });
  }
}

function createIssue (repo, todo, conf, cb) {
  console.log("(fake) Creating issue…");
  getCommentText(repo, todo, conf, function (err, text) {
    if (err) {
      return cb(err);
    }

    github.createIssue(repo, todo.title, text, [todo.label], cb);
  });
}

function commentIssue (repo, todo, conf, cb) {
  console.log("(fake) Commenting issue #" + todo.issue + "…");
  getCommentText(repo, todo, conf, function (err, text) {
    if (err) {
      return cb(err);
    }

    github.commentIssue(repo, todo.issue, text, cb);
  });
}

function getCommentText (repo, todo, conf, cb) {
  var text = "";

  // Link to file
  text += "Ref. [" + todo.file + ":" + todo.line + "](" + github.getFileUrl(repo, todo.file, todo.sha, todo.line) + ")";

  // Add code information
  fs.readFile(path.resolve(todo.file), {encoding: "utf8"}, function (err, content) {
    if (err) {
      return cb(err);
    }

    var lines = content.split(/\r\n|\r|\n/);

    // Remove trailing new lines
    while (lines[lines.length - 1] === "") {
      lines.pop();
    }
    while (lines[0] === "") {
      lines.shift();
    }

    if (conf.context > 0) {
      // Extract: line to line + conf.context
      var extract = lines.slice(todo.line - 1, todo.line + conf.context).join("\n");
      if (todo.line + conf.context < lines.length) {
        extract += "\n…";
      }

      // TODO syntax highlighting?
      text += "\n\n```\n" + extract + "\n```\n";
    }

    if (conf.signature) {
      text += "\n" + conf.signature;
    }

    cb(null, text);
  });
}

function fromDiff (repo, diff, sha, conf, cb) {
  if (_.isFunction(conf)) {
    cb = conf;
    conf = null;
  }
  conf = _.merge({
    "onProgress": _.noop
  }, conf || {}, config.defaults);

  async.map(_.flatten(_.map(diff, function (file) {
    var addedLines = _.filter(file.lines, "add");
    return _.filter(_.map(addedLines, lineToTodoMapper(file.to, sha, conf)), "title");
  })), function (todo, cb) {
    createOrCommentIssue(repo, todo, conf, function (err, result) {
      conf.onProgress(err, result);
      cb(err, result);
    });
  }, cb);
}

// String, Sha → String → {file, sha, line, title, label}
function lineToTodoMapper (filename, sha, conf) {
  return function lineToTodo (line) {
    return _.merge({
      "file":   filename,
      "sha":    sha,
      "line":   line.ln
    }, extractTodoTitle(line.content, conf));
  };
}

// String → {title, label}
function extractTodoTitle (content, conf) {
  var result = null;

  var labels = {};
  _.each(conf, function (value, key) {
    if (key.match(/^label\./)) {
      var trigger = key.substring(6);
      if (conf["label-whitespace"]) {
        trigger += " ";
      }
      labels[trigger] = value;
    }
  });

  if (_.isString(content)) {
    _.find(Object.keys(labels), function (trigger) {
      var index;
      if (conf["case-sensitive"]) {
        index = content.indexOf(trigger);
      } else {
        index = content.toUpperCase().indexOf(trigger.toUpperCase());
      }

      if (index !== -1) {
        var title = content.substring(index + trigger.length).trim();
        if (title && !isCode(title)) {
          result = {
            "title":  title,
            "label":  labels[trigger]
          };
        }
        return true; // break
      }
    });
  }

  return result;
}

// TODO Better heuristic for code vs words detection

// Simple heuristic to detect if a title is really a title or some valid code
// String → Boolean
function isCode (string) {
  // If symbols are more than 20% of the code, it may be code more than human text
  var symbols = _.filter(string, isSymbol);

  return symbols.length / string.length > 0.20;
}

var RE_SYMBOL = /[^\sa-z0-9\u00E0-\u00FC]/i;
// Matches a symbol: non alphanumeric character
// Character → Boolean
function isSymbol (character) {
  return RE_SYMBOL.test(character);
}
<|endoftext|>


<commit_before>var _global_console = global.console,
    _global_crypto = global.crypto,
    _global_date_now = global.Date.now,
    _global_math_random = global.Math.random,
    _global_performance = global.performance;

var _isaac_rand = ISAAC.rand,
    _isaac_seed = ISAAC.seed;

var _random_is_seeded = false,
    _random_is_seed_supplied = false;

/**
 * seed
 *
 * Seeds PRNG with supplied random values.
 *
 * **DISCLAIMER!** Seeding with a poor values is an easiest way shoot your legs, so
 * do not seed until you're know what entropy is and how to obtail high-quality random values,
 * **DO NOT SEED WITH CONSTANT VALUE! YOU'LL GET NO RANDOMNESS FROM CONSTANT!**
 *
 * When no values supplied **and** PRNG hasn't been seeded yet, seeds with:
 *
 * - `crypto.getRandomValues` output (if available, fast method), **or**
 *
 * - special seeding steps are performed (fallback slow method):
 *      - collect entropy from `Date.now` (~ 10 bits), `Math.random` (~50 bits), `performance.now` (if available, µs resolution adds ~10 bits to `Date.now`'s entropy);
 *      - perform key derivation with 4096-rounded `PBKDF2-HMAC-SHA256` salted by `location.href` in order to make seed guessing impractical;
 *      - collect additional entropy from `performance.now` (if available, adds ~10 bits of entropy more);
 *      - seed PRNG with these values (containing ~50 bits of entropy at the worst case and up to ~80 bits at the best).
 *
 * Note that in case of fallback seeding you'll get warning into the console every time you request new random values.
 */
function Random_seed ( buffer ) {
    if ( buffer !== undefined ) {
        _isaac_seed(buffer);
        _random_is_seed_supplied = true;
    }
    else if ( !_random_is_seeded ) {
        if ( _global_crypto !== undefined ) {
            buffer = new Uint32Array(256);
            _global_crypto.getRandomValues(buffer);

            _isaac_seed(buffer);

            _random_is_seed_supplied = true;
        }
        else {
            buffer = new Float64Array(3);
            buffer[0] = _global_date_now();
            buffer[1] = _global_math_random();
            if ( _global_performance !== undefined ) buffer[2] = _global_performance.now();

            buffer = new Uint32Array( pbkdf2_hmac_sha256_bytes( buffer.buffer, global.location.href, 4096, 1024 ).buffer );

            if ( _global_performance !== undefined ) buffer[0] ^= 1000 * _global_performance.now() | 0;

            _isaac_seed(buffer);

            _random_is_seed_supplied = false;
        }
    }
    else {
        throw new IllegalArgumentError("bad seed");
    }

    _random_is_seeded = true;
}

/**
 * getValues
 *
 * Populates the buffer with cryptographically secure random values.
 * A drop-in replacement for `crypto.getRandomValues`.
 *
 * First call also seeds the PRNG if it hasn't been seeded yet, so it may halt for a moment.
 *
 * Take a note that, as PRNG is guaranteed to have cycles at least 2^40 values long,
 * it must be reseeded from time to time. It happens automatically every time 2^40 values
 * are produced and performed by calling seeding routine without arguments (see docs above).
 *
 * Also note that in case of fallback seeding (see docs) you'll get warning into the console every time you request new random values.
 */
function Random_getValues ( buffer ) {
    if ( !_random_is_seeded )
        Random_seed();

    if ( !_random_is_seed_supplied && _global_console !== undefined )
        _global_console.warn("Random number generator hasn't been properly seeded and produces potentially weak values, though it has at least 50 bits of entropy.");

    if ( !is_buffer(buffer) && !is_typed_array(buffer) )
        throw new TypeError("unexpected buffer type");

    var bpos = buffer.byteOffset || 0,
        blen = buffer.byteLength || buffer.length,
        bytes = new Uint8Array( ( buffer.buffer || buffer ), bpos, blen ),
        i, r;

    // TODO reseed upon reaching 2^40 mark
    for ( i = 0; i < blen; i++ ) {
        if ( (i & 3) === 0 ) r = _isaac_rand();
        bytes[i] = r;
        r >>>= 8;
    }
}
<commit_msg>PRNG: tweak the mechanics of Random.getValues - use crypto.getRandomValues directly when available, instead of using it to seed ISAAC - add an entropy estimator, and forbid manual seeds with low entropy - split the weak seeding into a separate function so the logic is clearer - greatly reduce the documentation of the weak seeding, we don't want users to rely on it<commit_after>var _global_console = global.console,
    _global_crypto = global.crypto,
    _global_date_now = global.Date.now,
    _global_math_random = global.Math.random,
    _global_performance = global.performance;

var _isaac_rand = ISAAC.rand,
    _isaac_seed = ISAAC.seed;

var _random_weak_seeded = false,
    _bytes_entropy_required = 64,
    _random_seeded = false;

function _bytes_entropy_estimate(byte_array) {
    var counts = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
    for (var i = 0; i < byte_array.length; i++) {
        var lo = byte_array[i] & 0xf;
        var hi = (byte_array[i] >> 4) & 0xf;
        counts[lo]++;
        counts[hi]++;
    }
    return counts.map(function(x) {
        return x ? -(x/byte_array.length/2 * Math.log(x/byte_array.length/2) / Math.log(2)): 0;
    }).reduce(function(a, b){
        return a+b;
    }, 0) / 4 * byte_array.length;
}

function _random_weak_seed() {
    var buffer = new Float64Array(3);
    buffer[0] = _global_date_now();
    buffer[1] = _global_math_random();
    if ( _global_performance !== undefined ) buffer[2] = _global_performance.now();

    buffer = new Uint32Array( pbkdf2_hmac_sha256_bytes( buffer.buffer, global.location.href, 4096, 1024 ).buffer );

    if ( _global_performance !== undefined ) buffer[0] ^= 1000 * _global_performance.now() | 0;

    _isaac_seed(buffer);
    _random_weak_seeded = true;
}

/**
 * seed
 *
 * Seeds PRNG with supplied random values if these values have enough entropy.
 * Returns true if seeding took place, otherwise false.
 *
 * A false return value means the input was not secure; however a true return
 * value does NOT mean the input is necessarily secure.
 *
 * The input buffer will be zeroed to discourage reuse. You should not copy it
 * or use it anywhere else before passing it into this function.
 *
 * **DISCLAIMER!** Seeding with a poor values is an easiest way shoot your legs, so
 * do not seed until you're know what entropy is and how to obtail high-quality random values,
 * **DO NOT SEED WITH CONSTANT VALUE! YOU'LL GET NO RANDOMNESS FROM CONSTANT!**
 */
function Random_seed ( buffer ) {
    var bpos = buffer.byteOffset || 0,
        blen = buffer.byteLength || buffer.length,
        bytes = new Uint8Array( ( buffer.buffer || buffer ), bpos, blen );

    if (_bytes_entropy_estimate(bytes) < _bytes_entropy_required) {
        return false;
    } else {
        _isaac_seed(bytes);
        // don't let the user use these bytes again
        for (var i=0; i<bytes.length; i++) bytes[i] = 0;
        return _random_seeded = true;
    }
}

/**
 * getValues
 *
 * Populates the buffer with cryptographically secure random values.
 * Delegates to `crypto.getRandomValues` if it is available.
 *
 * Otherwise, we fall back to asmCrypto's own implementation of the ISAAC PRNG.
 * This must be first seeded using `Random.seed`, which has requirements on
 * the randomness you give it.
 *
 * If you don't manually call `Random.seed`, and allow_weak is true, we will
 * proceed using a weak seed from things like high-resolution time and one
 * single value from the insecure Math.random(). In this case you'll still get
 * warnings into the console every time you request new random values.
 *
 * Take a note that, as PRNG is guaranteed to have cycles at least 2^40 values long,
 * it must be reseeded from time to time. It happens automatically every time 2^40 values
 * are produced and performed by calling seeding routine without arguments (see docs above).
 */
function Random_getValues ( buffer, allow_weak ) {
    if ( !is_buffer(buffer) && !is_typed_array(buffer) )
        throw new TypeError("unexpected buffer type");

    var bpos = buffer.byteOffset || 0,
        blen = buffer.byteLength || buffer.length,
        bytes = new Uint8Array( ( buffer.buffer || buffer ), bpos, blen ),
        i, r;

    // if we don't have a strong rng
    if ( !_global_crypto && !_random_seeded ) {
        // set a weak seed
        if ( allow_weak && !_random_weak_seeded )
            _random_weak_seed();

        // warn or error
        if ( !allow_weak ) {
            throw new Error("WebCrypto not available, and asmCrypto PRNG hasn't been properly seeded");
        } else if ( _global_console !== undefined ) {
            _global_console.warn("asmCrypto PRNG hasn't been properly seeded " +
                "and produces potentially weak values");
        }
    }

    if ( _global_crypto ) {
        _global_crypto.getRandomValues(bytes);
    }

    // TODO reseed upon reaching 2^40 mark
    for ( i = 0; i < blen; i++ ) {
        if ( (i & 3) === 0 ) r = _isaac_rand();
        bytes[i] ^= r;
        r >>>= 8;
    }
}
<|endoftext|>