<commit_before>var Block;
var bcoin = require('bcoin');
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var async = require('async');
var _ = require('underscore');

var config = require('../config');
var Transaction = mongoose.model('Transaction');
var Coin = mongoose.model('Coin');

var BlockSchema = new Schema({
  network: String,
  height: Number,
  hash: String,
  version: Number,
  merkleRoot: String,
  time: Date,
  timeNormalized: Date,
  nonce: Number,
  previousBlockHash: String,
  nextBlockHash: String,
  transactionCount: Number,
  size: Number,
  bits: Number,
  reward: Number,
  processed: Boolean
});

BlockSchema.index({hash: 1});
BlockSchema.index({height: 1});
BlockSchema.index({time: 1});
BlockSchema.index({timeNormalized: 1});
BlockSchema.index({previousBlockHash: 1});

BlockSchema.statics.addBlock = function(block, callback){
  var blockTime = block.ts * 1000;
  var blockTimeNormalized;
  var height;
  async.series([
    function(cb){
      Block.handleReorg(block, cb);
    },
    function(cb){
      Block.findOne({hash: bcoin.util.revHex(block.prevBlock)}, function(err, previousBlock) {
        if(err) {
          return cb(err);
        }
        if(!previousBlock &&
          block.prevBlock !== bcoin.network.get(config.network).genesis.hash &&
          block.prevBlock !== '0000000000000000000000000000000000000000000000000000000000000000'){
          return cb(new Error('No previous block found'));
        }
        blockTimeNormalized = blockTime;
        if(previousBlock && blockTime <= previousBlock.timeNormalized.getTime()) {
          blockTimeNormalized = previousBlock.timeNormalized.getTime() + 1;
        }
        height = (previousBlock && previousBlock.height + 1) || 1;
        Block.update({hash: block.rhash()}, {
          network: config.network,
          height: height,
          version: block.version,
          previousBlockHash: bcoin.util.revHex(block.prevBlock),
          merkleRoot: block.merkleRoot,
          time: new Date(blockTime),
          timeNormalized: new Date(blockTimeNormalized),
          bits: block.bits,
          nonce: block.nonce,
          transactionCount: block.txs.length,
          size: block._size,
          reward: bcoin.consensus.getReward(height, bcoin.network.get(config.network).halvingInterval)
        }, {upsert: true}, function(err){
          if (err){
            return cb(err);
          }
          if (!previousBlock){
            return cb();
          }
          previousBlock.nextBlockHash = block.rhash();
          previousBlock.save(cb);
        });
      });
    },
    function(cb){
      var transactionInserts = [];
      var coinInserts = [];

      async.series([
        function(cb){
          async.eachLimit(block.txs, config.maxPoolSize, function(tx, txCb){
            var transactionInsert = {
              txid: tx.rhash(),
              network: config.network,
              blockHeight: height,
              blockHash: block.rhash(),
              blockTime: blockTime,
              blockTimeNormalized: blockTimeNormalized,
              coinbase: tx.isCoinbase(),
              size: tx.getSize(),
              locktime: tx.locktime,
              wallets: []
            };
            async.eachOfLimit(tx.outputs, config.maxPoolSize, function (output, index, outputCb) {
              var address = output.getAddress();
              if (address) {
                address = address.toString(config.network);
              }
              mongoose.model('WalletAddress').find({ address: address }).lean().exec(function (err, wallets) {
                wallets = wallets.map((wallet) => wallet.wallet);
                coinInserts.push({
                  mintTxid: tx.rhash(),
                  mintIndex: index,
                  network: config.network,
                  mintHeight: height,
                  coinbase: tx.isCoinbase(),
                  value: output.value,
                  address: address,
                  script: output.script.raw,
                  spentHeight: -1,
                  wallets: wallets
                });
                wallets.forEach((wallet) => {
                  if (transactionInsert.wallets.indexOf(wallet) === -1){
                    transactionInsert.wallets.push(wallet);
                  }
                });
                outputCb();
              });
            }, function(err){
              transactionInserts.push(transactionInsert);
              txCb(err);
            });
          }, cb);
        },
        function(cb){
          async.eachLimit(block.txs, config.maxPoolSize, function(tx, txCb){
            async.eachLimit(tx.inputs, config.maxPoolSize, function (input, inputCb) {
              if (tx.isCoinbase()) {
                return inputCb();
              }
              var prevTx = input.prevout.rhash();
              Coin.collection.findOneAndUpdate({
                mintTxid: prevTx,
                mintIndex: input.prevout.index
              }, {
                  $set: {
                    spentTxid: tx.rhash(),
                    spentHeight: height
                  }
                }, {
                  projection: {
                    value: 1,
                    wallets: 1
                  }
                }, function (err, result) {
                  if (err) {
                    return inputCb(err);
                  }
                  if (!result.value) {
                    result.value = _.findWhere(coinInserts, {
                      mintTxid: prevTx,
                      mintIndex: input.prevout.index
                    });
                    if (!result.value) {
                      return inputCb(new Error('missing utxo'));
                    }
                    result.value.spentTxid = tx.rhash();
                    result.value.spentHeight = height;
                  }
                  let { wallets } = result.value;
                  var targetTx = _.findWhere(transactionInserts, { txid: tx.rhash() });
                  for (let wallet of wallets) {
                    if (targetTx.wallets.indexOf(wallet) === -1) {
                      targetTx.wallets.push(wallet);
                    }
                  }
                  inputCb();
                });
            }, txCb);
          },cb);
        }
      ], function(err){
        if (err) {
          return cb(err);
        }
        async.parallel([
          function (cb) {
            Transaction.collection.insertMany(transactionInserts, { ordered: false }, cb);
          },
          function (cb) {
            Coin.collection.insertMany(coinInserts, { ordered: false }, cb);
          }
        ], cb);
      });
    }
  ], function(err){
    if(err){
      return callback(err);
    }
    Block.update({hash: block.rhash()}, {$set: {processed:true}}, callback);
  });
};

BlockSchema.statics.getPoolInfo = function(coinbase){
  //TODO need to make this actually parse the coinbase input and map to miner strings
  // also should go somewhere else
  return 'miningPool';
};

BlockSchema.statics.getLocalTip = function(callback) {
  Block.find({processed: true, network: config.network}).sort({height: -1}).limit(1).exec(function(err, bestBlock) {
    if(err) {
      return callback(err);
    }
    bestBlock = bestBlock[0] || {height: 0};
    callback(null, bestBlock);
  });
};

BlockSchema.statics.getLocatorHashes = function(callback) {
  Block.find({processed: true, network: config.network}).sort({height: -1}).limit(30).exec(function(err, locatorBlocks) {
    if(err) {
      return callback(err);
    }
    if(locatorBlocks.length < 2) {
      return callback(null, [Array(65).join('0')]);
    }
    locatorBlocks = _.pluck(locatorBlocks, 'hash');
    callback(null, locatorBlocks);
  });
};

BlockSchema.statics.handleReorg = function(block, callback) {
  Block.getLocalTip(function(err, localTip) {
    if(err) {
      return callback(err);
    }
    if(block && localTip.hash === bcoin.util.revHex(block.prevBlock)) {
      return callback();
    }
    if(localTip.height === 0){
      return callback();
    }
    console.log(`Resetting tip to ${localTip.previousBlockHash}`);
    async.series([
      function(cb){
        Block.remove({ network: config.network, height: { $gte: localTip.height } }, cb);
      },
      function(cb){
        Transaction.remove({ network: config.network, blockHeight: { $gte: localTip.height } }, cb);
      },
      function(cb){
        Coin.remove({ network: config.network, mintHeight: { $gte: localTip.height } }, cb);
      },
      function (cb) {
        Coin.update({ network: config.network, spentHeight: { $gte: localTip.height } }, {
          $set: {spentTxid: null, spentHeight: -1}
        }, { multi: true }, cb);
      }
    ], callback);
  });
};

BlockSchema.statics._apiTransform = function(block, options){
  var transform = {
    hash: block.hash,
    height: block.height,
    version: block.version,
    size: block.size,
    merkleRoot: block.merkleRoot,
    time: block.time.getTime()/1000,
    timeNormalized: block.timeNormalized,
    nonce: block.nonce,
    bits: block.bits,
    difficulty: block.difficulty,
    chainWork: block.chainWork,
    previousBlockHash: block.previousBlockHash,
    nextBlockHash: block.nextBlockHash,
    reward: block.reward,
    isMainChain: block.mainChain,
    minedBy: Block.getPoolInfo(block.minedBy)
  };
  if(options && options.object){
    return transform;
  }
  return JSON.stringify(transform);
};

module.exports = Block = mongoose.model('Block', BlockSchema);<commit_msg>wallet dupes<commit_after>var Block;
var bcoin = require('bcoin');
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var async = require('async');
var _ = require('underscore');

var config = require('../config');
var Transaction = mongoose.model('Transaction');
var Coin = mongoose.model('Coin');

var BlockSchema = new Schema({
  network: String,
  height: Number,
  hash: String,
  version: Number,
  merkleRoot: String,
  time: Date,
  timeNormalized: Date,
  nonce: Number,
  previousBlockHash: String,
  nextBlockHash: String,
  transactionCount: Number,
  size: Number,
  bits: Number,
  reward: Number,
  processed: Boolean
});

BlockSchema.index({hash: 1});
BlockSchema.index({height: 1});
BlockSchema.index({time: 1});
BlockSchema.index({timeNormalized: 1});
BlockSchema.index({previousBlockHash: 1});

BlockSchema.statics.addBlock = function(block, callback){
  var blockTime = block.ts * 1000;
  var blockTimeNormalized;
  var height;
  async.series([
    function(cb){
      Block.handleReorg(block, cb);
    },
    function(cb){
      Block.findOne({hash: bcoin.util.revHex(block.prevBlock)}, function(err, previousBlock) {
        if(err) {
          return cb(err);
        }
        if(!previousBlock &&
          block.prevBlock !== bcoin.network.get(config.network).genesis.hash &&
          block.prevBlock !== '0000000000000000000000000000000000000000000000000000000000000000'){
          return cb(new Error('No previous block found'));
        }
        blockTimeNormalized = blockTime;
        if(previousBlock && blockTime <= previousBlock.timeNormalized.getTime()) {
          blockTimeNormalized = previousBlock.timeNormalized.getTime() + 1;
        }
        height = (previousBlock && previousBlock.height + 1) || 1;
        Block.update({hash: block.rhash()}, {
          network: config.network,
          height: height,
          version: block.version,
          previousBlockHash: bcoin.util.revHex(block.prevBlock),
          merkleRoot: block.merkleRoot,
          time: new Date(blockTime),
          timeNormalized: new Date(blockTimeNormalized),
          bits: block.bits,
          nonce: block.nonce,
          transactionCount: block.txs.length,
          size: block._size,
          reward: bcoin.consensus.getReward(height, bcoin.network.get(config.network).halvingInterval)
        }, {upsert: true}, function(err){
          if (err){
            return cb(err);
          }
          if (!previousBlock){
            return cb();
          }
          previousBlock.nextBlockHash = block.rhash();
          previousBlock.save(cb);
        });
      });
    },
    function(cb){
      var transactionInserts = [];
      var coinInserts = [];

      async.series([
        function(cb){
          async.eachLimit(block.txs, config.maxPoolSize, function(tx, txCb){
            var transactionInsert = {
              txid: tx.rhash(),
              network: config.network,
              blockHeight: height,
              blockHash: block.rhash(),
              blockTime: blockTime,
              blockTimeNormalized: blockTimeNormalized,
              coinbase: tx.isCoinbase(),
              size: tx.getSize(),
              locktime: tx.locktime,
              wallets: []
            };
            async.eachOfLimit(tx.outputs, config.maxPoolSize, function (output, index, outputCb) {
              var address = output.getAddress();
              if (address) {
                address = address.toString(config.network);
              }
              mongoose.model('WalletAddress').find({ address: address }).lean().exec(function (err, wallets) {
                wallets = wallets.map((wallet) => wallet.wallet);
                coinInserts.push({
                  mintTxid: tx.rhash(),
                  mintIndex: index,
                  network: config.network,
                  mintHeight: height,
                  coinbase: tx.isCoinbase(),
                  value: output.value,
                  address: address,
                  script: output.script.raw,
                  spentHeight: -1,
                  wallets: wallets
                });
                wallets.forEach((wallet) => {
                  var alreadyHas = transactionInsert.wallets.find((txWallet) => {
                    return txWallet.toHexString() === wallet.toHexString();
                  });
                  if (!alreadyHas) {
                    transactionInsert.wallets.push(wallet);
                  }
                });
                if (transactionInsert.wallets.length > 1){
                  throw new Error();
                }
                outputCb();
              });
            }, function(err){
              transactionInserts.push(transactionInsert);
              txCb(err);
            });
          }, cb);
        },
        function(cb){
          async.eachLimit(block.txs, config.maxPoolSize, function(tx, txCb){
            async.eachLimit(tx.inputs, config.maxPoolSize, function (input, inputCb) {
              if (tx.isCoinbase()) {
                return inputCb();
              }
              var prevTx = input.prevout.rhash();
              Coin.collection.findOneAndUpdate({
                mintTxid: prevTx,
                mintIndex: input.prevout.index
              }, {
                  $set: {
                    spentTxid: tx.rhash(),
                    spentHeight: height
                  }
                }, {
                  projection: {
                    wallets: 1
                  }
                }, function (err, result) {
                  if (err) {
                    return inputCb(err);
                  }
                  if (!result.value) {
                    result.value = _.findWhere(coinInserts, {
                      mintTxid: prevTx,
                      mintIndex: input.prevout.index
                    });
                    if (!result.value) {
                      return inputCb(new Error('missing utxo'));
                    }
                    result.value.spentTxid = tx.rhash();
                    result.value.spentHeight = height;
                  }
                  let { wallets } = result.value;
                  var targetTx = _.findWhere(transactionInserts, { txid: tx.rhash() });
                  wallets.forEach((wallet) => {
                    var alreadyHas = targetTx.wallets.find((targetTxWallet) => {
                      return targetTxWallet.toHexString() === wallet.toHexString();
                    });
                    if (!alreadyHas){
                      targetTx.wallets.push(wallet);
                    }
                  });
                  if (targetTx.wallets.length > 1) {
                    throw new Error();
                  }
                  inputCb();
                });
            }, txCb);
          },cb);
        }
      ], function(err){
        if (err) {
          return cb(err);
        }
        async.parallel([
          function (cb) {
            Transaction.collection.insertMany(transactionInserts, { ordered: false }, cb);
          },
          function (cb) {
            Coin.collection.insertMany(coinInserts, { ordered: false }, cb);
          }
        ], cb);
      });
    }
  ], function(err){
    if(err){
      return callback(err);
    }
    Block.update({hash: block.rhash()}, {$set: {processed:true}}, callback);
  });
};

BlockSchema.statics.getPoolInfo = function(coinbase){
  //TODO need to make this actually parse the coinbase input and map to miner strings
  // also should go somewhere else
  return 'miningPool';
};

BlockSchema.statics.getLocalTip = function(callback) {
  Block.find({processed: true, network: config.network}).sort({height: -1}).limit(1).exec(function(err, bestBlock) {
    if(err) {
      return callback(err);
    }
    bestBlock = bestBlock[0] || {height: 0};
    callback(null, bestBlock);
  });
};

BlockSchema.statics.getLocatorHashes = function(callback) {
  Block.find({processed: true, network: config.network}).sort({height: -1}).limit(30).exec(function(err, locatorBlocks) {
    if(err) {
      return callback(err);
    }
    if(locatorBlocks.length < 2) {
      return callback(null, [Array(65).join('0')]);
    }
    locatorBlocks = _.pluck(locatorBlocks, 'hash');
    callback(null, locatorBlocks);
  });
};

BlockSchema.statics.handleReorg = function(block, callback) {
  Block.getLocalTip(function(err, localTip) {
    if(err) {
      return callback(err);
    }
    if(block && localTip.hash === bcoin.util.revHex(block.prevBlock)) {
      return callback();
    }
    if(localTip.height === 0){
      return callback();
    }
    console.log(`Resetting tip to ${localTip.previousBlockHash}`);
    async.series([
      function(cb){
        Block.remove({ network: config.network, height: { $gte: localTip.height } }, cb);
      },
      function(cb){
        Transaction.remove({ network: config.network, blockHeight: { $gte: localTip.height } }, cb);
      },
      function(cb){
        Coin.remove({ network: config.network, mintHeight: { $gte: localTip.height } }, cb);
      },
      function (cb) {
        Coin.update({ network: config.network, spentHeight: { $gte: localTip.height } }, {
          $set: {spentTxid: null, spentHeight: -1}
        }, { multi: true }, cb);
      }
    ], callback);
  });
};

BlockSchema.statics._apiTransform = function(block, options){
  var transform = {
    hash: block.hash,
    height: block.height,
    version: block.version,
    size: block.size,
    merkleRoot: block.merkleRoot,
    time: block.time.getTime()/1000,
    timeNormalized: block.timeNormalized,
    nonce: block.nonce,
    bits: block.bits,
    difficulty: block.difficulty,
    chainWork: block.chainWork,
    previousBlockHash: block.previousBlockHash,
    nextBlockHash: block.nextBlockHash,
    reward: block.reward,
    isMainChain: block.mainChain,
    minedBy: Block.getPoolInfo(block.minedBy)
  };
  if(options && options.object){
    return transform;
  }
  return JSON.stringify(transform);
};

module.exports = Block = mongoose.model('Block', BlockSchema);<|endoftext|>


<commit_before>"use strict";
/**
 * This is an internal module. See {@link MatrixEvent} and {@link RoomEvent} for
 * the public classes.
 * @module models/event
 */

/**
 * Enum for event statuses.
 * @readonly
 * @enum {string}
 */
module.exports.EventStatus = {
    UNKNOWN: "unknown",
    SENT: "sent",
    NOT_SENT: "not_sent",
    SENDING: "sending",
    INCOMING: "incoming"
};

/**
 * Construct a Matrix Event object
 * @constructor
 * @param {Object} event The raw event to be wrapped in this DAO
 * @prop {Object} event The raw event. Do not access this property
 * directly unless you absolutely have to. Prefer the getter methods defined on
 * this class. Using the getter methods shields your app from
 * changes to event JSON between Matrix versions.
 * @prop {RoomMember} sender The room member who sent this event, or null e.g.
 * this is a presence event.
 * @prop {RoomMember} target The room member who is the target of this event, e.g.
 * the invitee, the person being banned, etc.
 * @prop {EventStatus} status The sending status of the event.
 */
module.exports.MatrixEvent = function MatrixEvent(event) {
    this.event = event || {};
    this.sender = null;
    this.target = null;
    this.status = null;
};
module.exports.MatrixEvent.prototype = {
    /**
     * Get the event_id for this event.
     * @return {string} The event ID, e.g. $143350589368169JsLZx:localhost
     *
     */
    getId: function() {
        return this.event.event_id;
    },

    /**
     * Get the user_id for this event.
     * @return {string} The user ID, e.g. @alice:matrix.org
     */
    getSender: function() {
        return this.event.user_id;
    },

    /**
     * Get the type of event.
     * @return {string} The event type, e.g. m.room.message
     */
    getType: function() {
        return this.event.type;
    },

    /**
     * Get the room_id for this event. This will return undefined
     * for m.presence events.
     * @return {string} The room ID, e.g. !cURbafjkfsMDVwdRDQ:matrix.org
     *
     */
    getRoomId: function() {
        return this.event.room_id;
    },

    /**
     * Get the timestamp of this event.
     * @return {Number} The event timestamp, e.g. 1433502692297
     */
    getTs: function() {
        return this.event.origin_server_ts;
    },

    /**
     * Get the event content JSON.
     * @return {Object} The event content JSON, or an empty object.
     */
    getContent: function() {
        return this.event.content || {};
    },

    /**
     * Get the age of this event. This represents the age of the event when the
     * event arrived at the device, and not the age of the event when this
     * function was called.
     * @return {Number} The age of this event in milliseconds.
     */
    getAge: function() {
        return this.event.age;
    },

    /**
     * Get the event state_key if it has one. This will return undefined
     *  for message events.
     * @return {string} The event's state_key.
     */
    getStateKey: function() {
        return this.event.state_key;
    },

    /**
     * Check if this event is a state event.
     * @return {boolean} True if this is a state event.
     */
    isState: function() {
        return this.event.state_key !== undefined;
    }
};
<commit_msg>Add getPrevContent function and mark it's situational presence.<commit_after>"use strict";
/**
 * This is an internal module. See {@link MatrixEvent} and {@link RoomEvent} for
 * the public classes.
 * @module models/event
 */

/**
 * Enum for event statuses.
 * @readonly
 * @enum {string}
 */
module.exports.EventStatus = {
    UNKNOWN: "unknown",
    SENT: "sent",
    NOT_SENT: "not_sent",
    SENDING: "sending",
    INCOMING: "incoming"
};

/**
 * Construct a Matrix Event object
 * @constructor
 * @param {Object} event The raw event to be wrapped in this DAO
 * @prop {Object} event The raw event. Do not access this property
 * directly unless you absolutely have to. Prefer the getter methods defined on
 * this class. Using the getter methods shields your app from
 * changes to event JSON between Matrix versions.
 * @prop {RoomMember} sender The room member who sent this event, or null e.g.
 * this is a presence event.
 * @prop {RoomMember} target The room member who is the target of this event, e.g.
 * the invitee, the person being banned, etc.
 * @prop {EventStatus} status The sending status of the event.
 */
module.exports.MatrixEvent = function MatrixEvent(event) {
    this.event = event || {};
    this.sender = null;
    this.target = null;
    this.status = null;
};
module.exports.MatrixEvent.prototype = {
    /**
     * Get the event_id for this event.
     * @return {string} The event ID, e.g. $143350589368169JsLZx:localhost
     *
     */
    getId: function() {
        return this.event.event_id;
    },

    /**
     * Get the user_id for this event.
     * @return {string} The user ID, e.g. @alice:matrix.org
     */
    getSender: function() {
        return this.event.user_id;
    },

    /**
     * Get the type of event.
     * @return {string} The event type, e.g. m.room.message
     */
    getType: function() {
        return this.event.type;
    },

    /**
     * Get the room_id for this event. This will return undefined
     * for m.presence events.
     * @return {string} The room ID, e.g. !cURbafjkfsMDVwdRDQ:matrix.org
     *
     */
    getRoomId: function() {
        return this.event.room_id;
    },

    /**
     * Get the timestamp of this event.
     * @return {Number} The event timestamp, e.g. 1433502692297
     */
    getTs: function() {
        return this.event.origin_server_ts;
    },

    /**
     * Get the event content JSON.
     * @return {Object} The event content JSON, or an empty object.
     */
    getContent: function() {
        return this.event.content || {};
    },

    /**
     * Get the previous event content JSON. This will only return something for
     * state events which exist in the timeline.
     * @return {Object} The previous event content JSON, or an empty object.
     */
    getPrevContent: function() {
        return this.event.prev_content || {};
    },

    /**
     * Get the age of this event. This represents the age of the event when the
     * event arrived at the device, and not the age of the event when this
     * function was called.
     * @return {Number} The age of this event in milliseconds.
     */
    getAge: function() {
        return this.event.age;
    },

    /**
     * Get the event state_key if it has one. This will return undefined
     *  for message events.
     * @return {string} The event's state_key.
     */
    getStateKey: function() {
        return this.event.state_key;
    },

    /**
     * Check if this event is a state event.
     * @return {boolean} True if this is a state event.
     */
    isState: function() {
        return this.event.state_key !== undefined;
    }
};
<|endoftext|>


<commit_before>/*jslint indent: 2*/
/*globals require: true*/

var getLines = function (file, cb, async) {
  var fs = require('fs'), splitLines, data;
  splitLines = function (data) {
    data = data.toString();
    var lines;
    if (data.indexOf('\r\n') > -1) {
      lines = data.split('\r\n');
    } else if (data.indexOf('\n') > -1) {
      lines = data.split('\n');
    } else if (data.indexOf('\r') > -1) {
      lines = data.split('\r');
    } else { // mostly it's only one line
      lines = [data];
    }
    return lines.filter(function (line) {
      return line !== '';
    });
  };
  if (async) {
    fs.readFile(file, function (err, data) {
      if (err) {
        throw err;
      }
      cb(splitLines(data));
    });
  } else {
    data = fs.readFileSync(file);
    return splitLines(data);
  }
};

/**
 * If a string is inside quotes, the quotes will be removed
 * Very simple and not foolproof yet. It doesn't care about
 * escaped/not escaped strings. So you can have thing like this;
 * "lorem ipsum" dolor sit"
 * and you will receive:
 * lorem ipsum" dolor sit
 *
 * FIXME
 * is it a bug?
 */
var fixQuoted = function (str) {
  if (
    (str[0] === '"' && str[str.length - 1] === '"') ||
    (str[0] === "'" && str[str.length - 1] === "'")
  ) {

    return str.substr(1, str.length - 2);
  }

  return str;

};

var inheritDefault = function(block, _default, key) {
  var out, _inheritDefault, _key;
  _default = typeof _default === 'object' ? _default : {};

  if (typeof block !== 'object') {
    return undefined;
  }

  _inheritDefault = function(key) {
    return block.hasOwnProperty(key) ? block[key] : _default[key];
  };

  if (typeof key === 'string') {
    out = _inheritDefault(key);
  } else {
    out = {};
    for (_key in block) {
      out[_key] = block[_key];
    };
    for (_key in _default) {
      out[_key] = _inheritDefault(_key);
    }
  }

  return out;
};

/**
 * Parses a .ini file and convert's it's content to a JS object
 * Parser regexps are from the Config::Simple Perl module
 * @class IniReader
 * @constructor
 */
var IniReader = function (cfg) {
  // backward compatibility
  // in first versions the first argument was the file name and the second was
  // the async flag
  if (typeof cfg === 'string') {
    cfg = {
      file: cfg
    };
    if (typeof arguments[1] === 'boolean') {
      cfg.async = arguments[1];
    }
  }
  cfg = cfg || {};
  this.async = !!cfg.async;
  this.file = cfg.file || null;
  this.inheritDefault = !!cfg.inheritDefault;
};
require('util').inherits(IniReader, require('events').EventEmitter);
/**
 * Regexp to get the group names
 */
IniReader.prototype.groupRex = /^\s*\[\s*([^\]]+)\s*\]$/;

/**
 * Regexp to get key/value pairs
 */
IniReader.prototype.keyValueRex = /^\s*([^=]*\w)\s*=\s*(.*)\s*$/;

/**
 * @method load
 * @param String file
 * Loads a ini file
 **/
IniReader.prototype.load = IniReader.prototype.init = function (file) {
  if (typeof file === 'string') {
    this.file = file;
  }
  if (!this.file) {
    throw new Error('No file name given');
  }
  if (this.async) {
    getLines(this.file, function (lines) {
      this.lines = lines;
      this.values = this.parseFile();
      this.emit('fileParse');
    }.bind(this), true);
  } else {
    this.lines = getLines(this.file);
    this.values = this.parseFile();
    this.emit('fileParse');
  }
};

/**
  * Tries to find a group name in a line
  * @method parseSectionHead
  * @type {String|False}
  * @returns the group name if found or false
  */
IniReader.prototype.parseSectionHead = function (line) {
  var groupMatch = line.match(this.groupRex);
  return groupMatch ? groupMatch[1] : false;
};

/**
  * Tries to find a key/value pair in a line
  * @method keyValueMatch
  * @type {Object|False}
  * @returns the key value pair in an object ({key: 'key', value;'value'}) if found or false
  */
IniReader.prototype.keyValueMatch = function (line) {
  var keyValMatch = line.match(this.keyValueRex);
  return keyValMatch ? {key: keyValMatch[1], value: keyValMatch[2]} : false;
};

/**
  * Parses an init file, and extracts blocks with keys and values
  * @method parseFile
  * @returns the conf tree
  * @type Object
  */
IniReader.prototype.parseFile = function () {

  var output, lines, groupName, keyVal, line, currentSection, lineNumber;

  // regular expressions to clear, validate and get the values
  const skipLineRex = /^\s*(\n|\#|;)/,
    chompRex = /(?:\n|\r)$/,
    nonWhitespaceRex = /\S/;

  output = {};
  lines = this.lines;

  lineNumber = 0;

  while (line = lines.shift()) {

    lineNumber += 1;
    // skip comments and empty lines
    if (skipLineRex.test(line) || !nonWhitespaceRex.test(line)) {
      continue;
    }

    line = line.replace(chompRex, '');
    line = line.trim();

    // block name
    groupName = this.parseSectionHead(line);
    if (groupName) {
      currentSection = groupName;
      if (!output[currentSection]) {
        output[currentSection] = {};
      }
      continue;
    }

    // key/value pairs
    keyVal = this.keyValueMatch(line);
    if (keyVal) {
      if (currentSection) {
        output[currentSection][keyVal.key] = fixQuoted(keyVal.value);
        continue;
      }
    }

    // if we came this far, the syntax couldn't be validated
    throw new Error("syntax error in line " + lineNumber);

  }

  return output;
};
/**
  * @method getBlock
  * @returns A block of the conf tree
  * @type Object
  * @deprecated
  */
IniReader.prototype.getBlock = function (block) {
  return this.param(block);
};
/**
  * @method getValue
  * @returns the value of the key
  * @param String block The name of the block where the key should be defined
  * @param String key The name of the key which value should be returned
  * @deprecated
  */
IniReader.prototype.getValue = function (block, key) {
  var param = block;
  if (typeof key !== 'undefined') {
    param += '.' + key;
  }
  return this.getParam(param);
};
/**
  * @method getParam
  * @returns the value of the key
  * @param String param The name of the block where the key should be defined
  */
IniReader.prototype.getParam = function (param) {
  var output = this.values,
  block, key,
  useDefault = this.inheritDefault && this.values.hasOwnProperty('DEFAULT');

  if (param) {
    param = param.split('.');
    block = param[0];
    key = param[1];

    if (typeof block === 'string') {
      if (typeof key === 'string' && typeof output[block] !== 'undefined') {
        if (useDefault) {
          output = inheritDefault(output[block], this.values.DEFAULT, key);
        } else {
          output = output[block][key];
        }
      } else {
        if (useDefault) {
          output = inheritDefault(output[block], this.values.DEFAULT);
        } else
          output = output[block];
      }
    }
  } else if (useDefault) {
    output = {};
    for (var section in this.values) {
      output[section] = {};
      for (key in this.values[section]) {
        output[section][key] = this.values[section][key];
      };

      if (section != 'DEFAULT') {
        for (key in this.values.DEFAULT) {
          if (!output[section].hasOwnProperty(key)) {
            output[section][key] = output.DEFAULT[key];
          }
        }
      }
    }
  }

  return output;
};
/**
 * @method setParam
 */
IniReader.prototype.setParam = function (prop, value) {
  if (typeof this.values !== 'object') {
    this.values = {};
  }
  var propKeys = prop.split('.'),
    propKeysLen = propKeys.length,
    ref = this.values;
  if (propKeysLen > 0) {
    propKeys.forEach(function (key, index) {
      if (!ref[key]) {
        ref[key] = {};
      }
      if (index < propKeysLen - 1) {
        ref = ref[key];
      } else {
        ref[key] = value;
      }
    }, this);
  }
};
/**
 * @method param
 * **/
IniReader.prototype.param = function (prop, value) {
  if (typeof value === 'undefined') {
    return this.getParam(prop);
  } else {
    return this.setParam(prop, value);
  }
};

IniReader.prototype.getLe = function (le) {
  return typeof le === 'string' && (le === '\n' || le === '\r\n' || le === '\r') ? le : '\n';
};

IniReader.prototype.serialize = function (le) {
  var output = '',
    group, ws = /\s+/;

  le = this.getLe(le);

  Object.keys(this.values).forEach(function (group) {
    output += le + '[' + group + ']' + le;

    Object.keys(this.values[group]).forEach(function (key) {
      var value = this.values[group][key];
      if (ws.test(value)) {
        if (value.indexOf('"') > -1) {
          value = "'" + value + "'";
        } else {
          value = '"' + value + '"';
        }
      }
      output += key + '=' + value + le;
    }, this);
  }, this);
  return output;
};

IniReader.prototype.write = function (file, le) {
  if (!file) {
    file = this.file;
  }

  le = this.getLe(le);

  var now = new Date(),
    output = '; IniReader' + le + '; ' + now.getFullYear() + '-' +
      (now.getMonth() + 1) + '-' + now.getDate() + le,
    fs = require('fs'),
    group, item, value, ws = /\s+/;

  output += this.serialize(le);

  if (this.async) {
    fs.writeFile(file, output, function (err) {
      if (err) {
        throw err;
      }
      this.emit('fileWritten', file);
    }.bind(this));
  } else {
    fs.writeFileSync(file, output);
    this.emit('fileWritten', file);
  }
};

exports.IniReader = IniReader;
<commit_msg>Simplify setting default values<commit_after>/*jslint indent: 2*/
/*globals require: true*/

var getLines = function (file, cb, async) {
  var fs = require('fs'), splitLines, data;
  splitLines = function (data) {
    data = data.toString();
    var lines;
    if (data.indexOf('\r\n') > -1) {
      lines = data.split('\r\n');
    } else if (data.indexOf('\n') > -1) {
      lines = data.split('\n');
    } else if (data.indexOf('\r') > -1) {
      lines = data.split('\r');
    } else { // mostly it's only one line
      lines = [data];
    }
    return lines.filter(function (line) {
      return line !== '';
    });
  };
  if (async) {
    fs.readFile(file, function (err, data) {
      if (err) {
        throw err;
      }
      cb(splitLines(data));
    });
  } else {
    data = fs.readFileSync(file);
    return splitLines(data);
  }
};

/**
 * If a string is inside quotes, the quotes will be removed
 * Very simple and not foolproof yet. It doesn't care about
 * escaped/not escaped strings. So you can have thing like this;
 * "lorem ipsum" dolor sit"
 * and you will receive:
 * lorem ipsum" dolor sit
 *
 * FIXME
 * is it a bug?
 */
var fixQuoted = function (str) {
  if (
    (str[0] === '"' && str[str.length - 1] === '"') ||
    (str[0] === "'" && str[str.length - 1] === "'")
  ) {

    return str.substr(1, str.length - 2);
  }

  return str;

};

var inheritDefault = function (block, _default) {
  var _inheritDefault, _key;
  _default = typeof _default === 'object' ? _default : {};

  if (typeof block === 'object') {
    for (_key in _default) {
      if (!(_key in block)) {
        block[_key] = _default[_key];
      }
    }
  }

  return block;
};

/**
 * Parses a .ini file and convert's it's content to a JS object
 * Parser regexps are from the Config::Simple Perl module
 * @class IniReader
 * @constructor
 */
var IniReader = function (cfg) {
  // backward compatibility
  // in first versions the first argument was the file name and the second was
  // the async flag
  if (typeof cfg === 'string') {
    cfg = {
      file: cfg
    };
    if (typeof arguments[1] === 'boolean') {
      cfg.async = arguments[1];
    }
  }
  cfg = cfg || {};
  this.async = !!cfg.async;
  this.file = cfg.file || null;
  this.inheritDefault = !!cfg.inheritDefault;
};
require('util').inherits(IniReader, require('events').EventEmitter);
/**
 * Regexp to get the group names
 */
IniReader.prototype.groupRex = /^\s*\[\s*([^\]]+)\s*\]$/;

/**
 * Regexp to get key/value pairs
 */
IniReader.prototype.keyValueRex = /^\s*([^=]*\w)\s*=\s*(.*)\s*$/;

/**
 * @method load
 * @param String file
 * Loads a ini file
 **/
IniReader.prototype.load = IniReader.prototype.init = function (file) {
  if (typeof file === 'string') {
    this.file = file;
  }
  if (!this.file) {
    throw new Error('No file name given');
  }
  if (this.async) {
    getLines(this.file, function (lines) {
      this.lines = lines;
      this.values = this.parseFile();
      this.emit('fileParse');
    }.bind(this), true);
  } else {
    this.lines = getLines(this.file);
    this.values = this.parseFile();
    this.emit('fileParse');
  }
};

/**
  * Tries to find a group name in a line
  * @method parseSectionHead
  * @type {String|False}
  * @returns the group name if found or false
  */
IniReader.prototype.parseSectionHead = function (line) {
  var groupMatch = line.match(this.groupRex);
  return groupMatch ? groupMatch[1] : false;
};

/**
  * Tries to find a key/value pair in a line
  * @method keyValueMatch
  * @type {Object|False}
  * @returns the key value pair in an object ({key: 'key', value;'value'}) if found or false
  */
IniReader.prototype.keyValueMatch = function (line) {
  var keyValMatch = line.match(this.keyValueRex);
  return keyValMatch ? {key: keyValMatch[1], value: keyValMatch[2]} : false;
};

/**
  * Parses an init file, and extracts blocks with keys and values
  * @method parseFile
  * @returns the conf tree
  * @type Object
  */
IniReader.prototype.parseFile = function () {

  var output, lines, groupName, keyVal, line, currentSection, lineNumber;

  // regular expressions to clear, validate and get the values
  var skipLineRex = /^\s*(\n|\#|;)/,
    chompRex = /(?:\n|\r)$/,
    nonWhitespaceRex = /\S/;

  output = {};
  lines = this.lines;

  lineNumber = 0;

  while (line = lines.shift()) {

    lineNumber += 1;
    // skip comments and empty lines
    if (skipLineRex.test(line) || !nonWhitespaceRex.test(line)) {
      continue;
    }

    line = line.replace(chompRex, '');
    line = line.trim();

    // block name
    groupName = this.parseSectionHead(line);
    if (groupName) {
      currentSection = groupName;
      if (!output[currentSection]) {
        output[currentSection] = {};
      }
      continue;
    }

    // key/value pairs
    keyVal = this.keyValueMatch(line);
    if (keyVal) {
      if (currentSection) {
        output[currentSection][keyVal.key] = fixQuoted(keyVal.value);
        continue;
      }
    }

    // if we came this far, the syntax couldn't be validated
    throw new Error("syntax error in line " + lineNumber);

  }

  return output;
};
/**
  * @method getBlock
  * @returns A block of the conf tree
  * @type Object
  * @deprecated
  */
IniReader.prototype.getBlock = function (block) {
  return this.param(block);
};
/**
  * @method getValue
  * @returns the value of the key
  * @param String block The name of the block where the key should be defined
  * @param String key The name of the key which value should be returned
  * @deprecated
  */
IniReader.prototype.getValue = function (block, key) {
  var param = block;
  if (typeof key !== 'undefined') {
    param += '.' + key;
  }
  return this.getParam(param);
};
/**
  * @method getParam
  * @returns the value of the key
  * @param String param The name of the block where the key should be defined
  */
IniReader.prototype.getParam = function (param) {
  var output = this.values,
  block, key, _block;

  if (this.inheritDefault && output.hasOwnProperty('DEFAULT')) {
    for (_block in output) {
      if (output.hasOwnProperty(_block)) {
        output[_block] = inheritDefault(output[_block], output.DEFAULT);
      }
    }
  }

  if (param) {
    param = param.split('.');
    block = param[0];
    key = param[1];

    if (block) {
      output = output[block];

      if (key) {
        output = output[key];
      }
    }
  }

  return output;
};
/**
 * @method setParam
 */
IniReader.prototype.setParam = function (prop, value) {
  if (typeof this.values !== 'object') {
    this.values = {};
  }
  var propKeys = prop.split('.'),
    propKeysLen = propKeys.length,
    ref = this.values;
  if (propKeysLen > 0) {
    propKeys.forEach(function (key, index) {
      if (!ref[key]) {
        ref[key] = {};
      }
      if (index < propKeysLen - 1) {
        ref = ref[key];
      } else {
        ref[key] = value;
      }
    }, this);
  }
};
/**
 * @method param
 * **/
IniReader.prototype.param = function (prop, value) {
  if (typeof value === 'undefined') {
    return this.getParam(prop);
  } else {
    return this.setParam(prop, value);
  }
};

IniReader.prototype.getLe = function (le) {
  return typeof le === 'string' && (le === '\n' || le === '\r\n' || le === '\r') ? le : '\n';
};

IniReader.prototype.serialize = function (le) {
  var output = '',
    group, ws = /\s+/;

  le = this.getLe(le);

  Object.keys(this.values).forEach(function (group) {
    output += le + '[' + group + ']' + le;

    Object.keys(this.values[group]).forEach(function (key) {
      var value = this.values[group][key];
      if (ws.test(value)) {
        if (value.indexOf('"') > -1) {
          value = "'" + value + "'";
        } else {
          value = '"' + value + '"';
        }
      }
      output += key + '=' + value + le;
    }, this);
  }, this);
  return output;
};

IniReader.prototype.write = function (file, le) {
  if (!file) {
    file = this.file;
  }

  le = this.getLe(le);

  var now = new Date(),
    output = '; IniReader' + le + '; ' + now.getFullYear() + '-' +
      (now.getMonth() + 1) + '-' + now.getDate() + le,
    fs = require('fs'),
    group, item, value, ws = /\s+/;

  output += this.serialize(le);

  if (this.async) {
    fs.writeFile(file, output, function (err) {
      if (err) {
        throw err;
      }
      this.emit('fileWritten', file);
    }.bind(this));
  } else {
    fs.writeFileSync(file, output);
    this.emit('fileWritten', file);
  }
};

exports.IniReader = IniReader;
<|endoftext|>


<commit_before>//-----------------------------------------------------------------------------
// Contstants
//-----------------------------------------------------------------------------

const moment = require('moment');

const INTERVALS = {
  year:   true,
  month:  true,
  week:   true,
  day:    true,
  hour:   true,
  minute: true,
  second: true
};

//-----------------------------------------------------------------------------
// Date Ranges
//-----------------------------------------------------------------------------

/**
 * DateRange class to store ranges and query dates.
 *
 * @constructor
 * @param {(Moment|Date)} start Start of interval
 * @param {(Moment|Date)} end End of interval
 *//**
 * DateRange class to store ranges and query dates.
 *
 * @constructor
 * @param {!Array} range Array containing start and end dates.
 *//**
 * DateRange class to store ranges and query dates.
 *
 * @constructor
 * @param {!String} range String formatted as an IS0 8601 time interval
 */
function DateRange(start, end) {
  var parts;
  var s = start;
  var e = end;

  if (arguments.length === 1 || end === undefined) {
    if (typeof start === 'object' && start.length === 2) {
      s = start[0];
      e = start[1];
    }
    else if (typeof start === 'string') {
      parts = start.split('/');
      s = parts[0];
      e = parts[1];
    }
  }

  this.start = moment(s);
  this.end   = moment(e);
}

/**
 * Constructor for prototype.
 *
 * @type {DateRange}
 */
DateRange.prototype.constructor = DateRange;

/**
 * Deep clone range.
 *
 * @return {!DateRange}
 */
DateRange.prototype.clone = function() {
  return moment().range(this.start, this.end);
};

/**
 * Determine if the current interval contains a given moment/date/range.
 *
 * @param {(Moment|Date|DateRange)} other Date to check
 * @param {!boolean} exclusive True if the to value is exclusive
 *
 * @return {!boolean}
 */
DateRange.prototype.contains = function(other, exclusive) {
  var start = this.start;
  var end   = this.end;

  if (other instanceof DateRange) {
    return start <= other.start && (end > other.end || (end.isSame(other.end) && !exclusive));
  }
  else {
    return start <= other && (end > other || (end.isSame(other) && !exclusive));
  }
};

/**
 * Determine if the current date range overlaps a given date range.
 *
 * @param {!DateRange} range Date range to check
 *
 * @return {!boolean}
 */
DateRange.prototype.overlaps = function(range) {
  return this.intersect(range) !== null;
};

/**
 * Determine the intersecting periods from one or more date ranges.
 *
 * @param {!DateRange} other A date range to intersect with this one
 *
 * @return {DateRange}
 */
DateRange.prototype.intersect = function(other) {
  var start = this.start;
  var end   = this.end;

  if ((start <= other.start) && (other.start < end) && (end < other.end)) {
    return new DateRange(other.start, end);
  }
  else if ((other.start < start) && (start < other.end) && (other.end <= end)) {
    return new DateRange(start, other.end);
  }
  else if ((other.start < start) && (start <= end) && (end < other.end)) {
    return this;
  }
  else if ((start <= other.start) && (other.start <= other.end) && (other.end <= end)) {
    return other;
  }

  return null;
};

/**
 * Merge date ranges if they intersect.
 *
 * @param {!DateRange} other A date range to add to this one
 *
 * @return {DateRange}
 */
DateRange.prototype.add = function(other) {
  if (this.overlaps(other)) {
    return new DateRange(moment.min(this.start, other.start), moment.max(this.end, other.end));
  }

  return null;
};

/**
 * Subtract one range from another.
 *
 * @param {!DateRange} other A date range to substract from this one
 *
 * @return {!Array<DateRange>}
 */
DateRange.prototype.subtract = function(other) {
  var start = this.start;
  var end   = this.end;

  if (this.intersect(other) === null) {
    return [this];
  }
  else if ((other.start <= start) && (start < end) && (end <= other.end)) {
    return [];
  }
  else if ((other.start <= start) && (start < other.end) && (other.end < end)) {
    return [new DateRange(other.end, end)];
  }
  else if ((start < other.start) && (other.start < end) && (end <= other.end)) {
    return [new DateRange(start, other.start)];
  }
  else if ((start < other.start) && (other.start < other.end) && (other.end < end)) {
    return [new DateRange(start, other.start), new DateRange(other.end, end)];
  }
};

/**
 * Iterate over the date range by a given date range, executing a function
 * for each sub-range.
 *
 * @param {(!DateRange|String)} range Date range to be used for iteration or
 *                                    shorthand string (shorthands:
 *                                    http://momentjs.com/docs/#/manipulating/add/)
 * @param {!DateRange~by} hollaback Callback
 * @param {!boolean} exclusive Indicate that the end of the range should not
 *                             be included in the iter.
 *
 * @return {DateRange} `this`
 */
DateRange.prototype.by = function(range, hollaback, exclusive) {
  if (typeof range === 'string') {
    _byString.call(this, range, hollaback, exclusive);
  }
  else {
    _byRange.call(this, range, hollaback, exclusive);
  }
  return this;
};

/**
 * Callback executed for each sub-range.
 *
 * @callback DateRange~by
 *
 * @param {!Moment} current Current moment object for iteration
 */

/**
 * @private
 */
function _byString(interval, hollaback, exclusive) {
  var current = moment(this.start);

  while (this.contains(current, exclusive)) {
    hollaback.call(this, current.clone());
    current.add(1, interval);
  }
}

/**
 * @private
 */
function _byRange(interval, hollaback, exclusive) {
  var div = this / interval;
  var l = Math.floor(div);

  if (l === Infinity) { return; }
  if (l === div && exclusive) {
    l--;
  }

  for (var i = 0; i <= l; i++) {
    hollaback.call(this, moment(this.start.valueOf() + interval.valueOf() * i));
  }
}

/**
 * Date range formatted as an [ISO8601 Time
 * Interval](http://en.wikipedia.org/wiki/ISO_8601#Time_intervals).
 *
 * @return {!String}
 */
DateRange.prototype.toString = function() {
  return this.start.format() + '/' + this.end.format();
};

/**
 * Date range in milliseconds. Allows basic coercion math of date ranges.
 *
 * @return {!number}
 */
DateRange.prototype.valueOf = function() {
  return this.end - this.start;
};

/**
 * Center date of the range.
 *
 * @return {!Moment}
 */
DateRange.prototype.center = function() {
  var center = this.start + this.diff() / 2;
  return moment(center);
};

/**
 * Date range toDate
 *
 * @return {!Array<Date>}
 */
DateRange.prototype.toDate = function() {
  return [this.start.toDate(), this.end.toDate()];
};

/**
 * Determine if this date range is the same as another.
 *
 * @param {!DateRange} other Another date range to compare to
 *
 * @return {!boolean}
 */
DateRange.prototype.isSame = function(other) {
  return this.start.isSame(other.start) && this.end.isSame(other.end);
};

/**
 * The difference of the end vs start.
 *
 * @param {number} unit Unit of difference, if no unit is passed in
 *                      milliseconds are returned. E.g.: `"days"`, `"months"`,
 *                      etc...
 *
 * @return {!number}
 */
DateRange.prototype.diff = function(unit) {
  return this.end.diff(this.start, unit);
};

//-----------------------------------------------------------------------------
// Moment Extensions
//-----------------------------------------------------------------------------

/**
 * Build a date range.
 *
 * @param {(Moment|Date)} start Start of range
 * @param {(Moment|Date)} end End of range
 *
 * @this {Moment}
 *
 * @return {!DateRange}
 */
moment.range = function(start, end) {
  if (start in INTERVALS) {
    return new DateRange(moment(this).startOf(start), moment(this).endOf(start));
  }
  else {
    return new DateRange(start, end);
  }
};

/**
 * Expose constructor
 *
 * @const
 */
moment.range.constructor = DateRange;

/**
 * @deprecated
 */
moment.fn.range = moment.range;

/**
 * Check if the current moment is within a given date range.
 *
 * @param {!DateRange} range Date range to check
 *
 * @this {Moment}
 *
 * @return {!boolean}
 */
moment.fn.within = function(range) {
  return range.contains(this._d);
};

//-----------------------------------------------------------------------------
// Export
//-----------------------------------------------------------------------------

module.exports = DateRange;
<commit_msg>Add documentation for methods that can return `null` (fixes #82)<commit_after>//-----------------------------------------------------------------------------
// Contstants
//-----------------------------------------------------------------------------

const moment = require('moment');

const INTERVALS = {
  year:   true,
  month:  true,
  week:   true,
  day:    true,
  hour:   true,
  minute: true,
  second: true
};

//-----------------------------------------------------------------------------
// Date Ranges
//-----------------------------------------------------------------------------

/**
 * DateRange class to store ranges and query dates.
 *
 * @constructor
 * @param {(Moment|Date)} start Start of interval
 * @param {(Moment|Date)} end End of interval
 *//**
 * DateRange class to store ranges and query dates.
 *
 * @constructor
 * @param {!Array} range Array containing start and end dates.
 *//**
 * DateRange class to store ranges and query dates.
 *
 * @constructor
 * @param {!String} range String formatted as an IS0 8601 time interval
 */
function DateRange(start, end) {
  var parts;
  var s = start;
  var e = end;

  if (arguments.length === 1 || end === undefined) {
    if (typeof start === 'object' && start.length === 2) {
      s = start[0];
      e = start[1];
    }
    else if (typeof start === 'string') {
      parts = start.split('/');
      s = parts[0];
      e = parts[1];
    }
  }

  this.start = moment(s);
  this.end   = moment(e);
}

/**
 * Constructor for prototype.
 *
 * @type {DateRange}
 */
DateRange.prototype.constructor = DateRange;

/**
 * Deep clone range.
 *
 * @return {!DateRange}
 */
DateRange.prototype.clone = function() {
  return moment().range(this.start, this.end);
};

/**
 * Determine if the current interval contains a given moment/date/range.
 *
 * @param {(Moment|Date|DateRange)} other Date to check
 * @param {!boolean} exclusive True if the to value is exclusive
 *
 * @return {!boolean}
 */
DateRange.prototype.contains = function(other, exclusive) {
  var start = this.start;
  var end   = this.end;

  if (other instanceof DateRange) {
    return start <= other.start && (end > other.end || (end.isSame(other.end) && !exclusive));
  }
  else {
    return start <= other && (end > other || (end.isSame(other) && !exclusive));
  }
};

/**
 * Determine if the current date range overlaps a given date range.
 *
 * @param {!DateRange} range Date range to check
 *
 * @return {!boolean}
 */
DateRange.prototype.overlaps = function(range) {
  return this.intersect(range) !== null;
};

/**
 * Determine the intersecting periods from one or more date ranges.
 *
 * @param {!DateRange} other A date range to intersect with this one
 *
 * @return {DateRange} Returns the intersecting date or `null` if the ranges do
 *                     not intersect
 */
DateRange.prototype.intersect = function(other) {
  var start = this.start;
  var end   = this.end;

  if ((start <= other.start) && (other.start < end) && (end < other.end)) {
    return new DateRange(other.start, end);
  }
  else if ((other.start < start) && (start < other.end) && (other.end <= end)) {
    return new DateRange(start, other.end);
  }
  else if ((other.start < start) && (start <= end) && (end < other.end)) {
    return this;
  }
  else if ((start <= other.start) && (other.start <= other.end) && (other.end <= end)) {
    return other;
  }

  return null;
};

/**
 * Merge date ranges if they intersect.
 *
 * @param {!DateRange} other A date range to add to this one
 *
 * @return {DateRange} Returns the new `DateRange` or `null` if they do not
 *                     overlap
 */
DateRange.prototype.add = function(other) {
  if (this.overlaps(other)) {
    return new DateRange(moment.min(this.start, other.start), moment.max(this.end, other.end));
  }

  return null;
};

/**
 * Subtract one range from another.
 *
 * @param {!DateRange} other A date range to substract from this one
 *
 * @return {!Array<DateRange>}
 */
DateRange.prototype.subtract = function(other) {
  var start = this.start;
  var end   = this.end;

  if (this.intersect(other) === null) {
    return [this];
  }
  else if ((other.start <= start) && (start < end) && (end <= other.end)) {
    return [];
  }
  else if ((other.start <= start) && (start < other.end) && (other.end < end)) {
    return [new DateRange(other.end, end)];
  }
  else if ((start < other.start) && (other.start < end) && (end <= other.end)) {
    return [new DateRange(start, other.start)];
  }
  else if ((start < other.start) && (other.start < other.end) && (other.end < end)) {
    return [new DateRange(start, other.start), new DateRange(other.end, end)];
  }
};

/**
 * Iterate over the date range by a given date range, executing a function
 * for each sub-range.
 *
 * @param {(!DateRange|String)} range Date range to be used for iteration or
 *                                    shorthand string (shorthands:
 *                                    http://momentjs.com/docs/#/manipulating/add/)
 * @param {!DateRange~by} hollaback Callback
 * @param {!boolean} exclusive Indicate that the end of the range should not
 *                             be included in the iter.
 *
 * @return {DateRange} `this`
 */
DateRange.prototype.by = function(range, hollaback, exclusive) {
  if (typeof range === 'string') {
    _byString.call(this, range, hollaback, exclusive);
  }
  else {
    _byRange.call(this, range, hollaback, exclusive);
  }
  return this;
};

/**
 * Callback executed for each sub-range.
 *
 * @callback DateRange~by
 *
 * @param {!Moment} current Current moment object for iteration
 */

/**
 * @private
 */
function _byString(interval, hollaback, exclusive) {
  var current = moment(this.start);

  while (this.contains(current, exclusive)) {
    hollaback.call(this, current.clone());
    current.add(1, interval);
  }
}

/**
 * @private
 */
function _byRange(interval, hollaback, exclusive) {
  var div = this / interval;
  var l = Math.floor(div);

  if (l === Infinity) { return; }
  if (l === div && exclusive) {
    l--;
  }

  for (var i = 0; i <= l; i++) {
    hollaback.call(this, moment(this.start.valueOf() + interval.valueOf() * i));
  }
}

/**
 * Date range formatted as an [ISO8601 Time
 * Interval](http://en.wikipedia.org/wiki/ISO_8601#Time_intervals).
 *
 * @return {!String}
 */
DateRange.prototype.toString = function() {
  return this.start.format() + '/' + this.end.format();
};

/**
 * Date range in milliseconds. Allows basic coercion math of date ranges.
 *
 * @return {!number}
 */
DateRange.prototype.valueOf = function() {
  return this.end - this.start;
};

/**
 * Center date of the range.
 *
 * @return {!Moment}
 */
DateRange.prototype.center = function() {
  var center = this.start + this.diff() / 2;
  return moment(center);
};

/**
 * Date range toDate
 *
 * @return {!Array<Date>}
 */
DateRange.prototype.toDate = function() {
  return [this.start.toDate(), this.end.toDate()];
};

/**
 * Determine if this date range is the same as another.
 *
 * @param {!DateRange} other Another date range to compare to
 *
 * @return {!boolean}
 */
DateRange.prototype.isSame = function(other) {
  return this.start.isSame(other.start) && this.end.isSame(other.end);
};

/**
 * The difference of the end vs start.
 *
 * @param {number} unit Unit of difference, if no unit is passed in
 *                      milliseconds are returned. E.g.: `"days"`, `"months"`,
 *                      etc...
 *
 * @return {!number}
 */
DateRange.prototype.diff = function(unit) {
  return this.end.diff(this.start, unit);
};

//-----------------------------------------------------------------------------
// Moment Extensions
//-----------------------------------------------------------------------------

/**
 * Build a date range.
 *
 * @param {(Moment|Date)} start Start of range
 * @param {(Moment|Date)} end End of range
 *
 * @this {Moment}
 *
 * @return {!DateRange}
 */
moment.range = function(start, end) {
  if (start in INTERVALS) {
    return new DateRange(moment(this).startOf(start), moment(this).endOf(start));
  }
  else {
    return new DateRange(start, end);
  }
};

/**
 * Expose constructor
 *
 * @const
 */
moment.range.constructor = DateRange;

/**
 * @deprecated
 */
moment.fn.range = moment.range;

/**
 * Check if the current moment is within a given date range.
 *
 * @param {!DateRange} range Date range to check
 *
 * @this {Moment}
 *
 * @return {!boolean}
 */
moment.fn.within = function(range) {
  return range.contains(this._d);
};

//-----------------------------------------------------------------------------
// Export
//-----------------------------------------------------------------------------

module.exports = DateRange;
<|endoftext|>


<commit_before>var coat = 36;
var jacket = 45;
var sweater = 60;
var profile;

function query(location) {
    $.ajax({
        type: 'GET',
        url: 'https://api.wunderground.com/api/dba29ab1640cac5b/hourly' + location + '.json',
        dataType: 'json',
        success: function(data) {processData(data);}
    });
}

function processData(data) {
    $('#searchResp').empty();
    $('#rec').show();
    var entries = data.hourly_forecast;
    var feels = entries[0].feelslike.english;
    var feelsMin = feels;
    var rain = false;
    var snow = false;
    for (var i = 1; i < 12; i++) {
        if (feelsMin > entries[i].feelslike.english) {
            feelsMin = entries[i].feelslike.english;
        }

        if (entries[i].condition.toLowerCase().includes("rain")) {
            rain = true;
        }
        if (entries[i].condition.toLowerCase().includes("snow")) {
            snow = true;
        }
    }

    var simp = 'No';
    var rec = 'You don\'t need a jacket';
    if (rain || snow) {
        simp = 'Yes';
    }
    if (snow) {
        rec = 'You should wear a coat today, it\'s snowing';
    } else if (rain) {
        if (feelsMin < coat) {
            rec = 'It\'s cold and raining out today, you need a coat';
        } else {
            rec = 'You should wear a jacket today, it\'s raining';
        }
    } else {
        if (feelsMin < coat) {
            simp = 'Yes';
            rec = 'It\'s cold out today, you need a coat';
        } else if (feelsMin < jacket) {
            simp = 'Yes';
            rec = 'You should wear a jacket today';
        } else if (feelsMin < sweater) {
            simp = 'No';
            rec = 'You only need a sweater';
        }
    }
    $('title').text('Do I Need a Jacket? ' + simp + '.');
    $('#simpResp').text(simp + '. ');
    $('#longResp').text(rec + '. The coldest it will feel today is ' + feelsMin + ' F.');
}

function search() {
    var query = $('#searchField').val();
    $('#rec').hide();
    $('body').append("");
    return false;
}

function processQuery(data) {
    var entries = data.RESULTS;
    $('#searchResp').empty();
    if (entries.length == 0) {
        $('#searchResp').append('No results.')
    } else {
        $('#searchResp').append('

');
    }
    for (var i = 0; i < entries.length; i++) {
        var entry = entries[i];
        $('#searchResp ul').append('
' + entry.name + '
')
    }
}

function onSignIn(googleUser) {
    profile = googleUser.getBasicProfile();
    $.ajax({
        type: 'GET',
        url: 'https://pantherman594.com/jacket.php?id=' + profile.getId(),
        success: function(data) {login(data);}
    });
}

function login(data) {
    $('#settings').show();
    if (data != "Error") {
        data = data.split(";");
        coat = data[0];
        jacket = data[1];
        sweater = data[2];
    }

    $('#coat').val(coat);
    $('#jacket').val(jacket);
    $('#sweater').val(sweater);
}

function settings() {
    if (!$('#coat').val()) $('#coat').val(coat);
    if (!$('#jacket').val()) $('#jacket').val(jacket);
    if (!$('#sweater').val()) $('#sweater').val(sweater);
    $.ajax({
        type: 'GET',
        url: 'https://pantherman594.com/jacket.php?id=' + profile.getId() + '&coat=' + $('#coat').val() + '&jacket=' + $('#jacket').val() + '&sweater=' + $('#sweater').val(),
        success: function(data) {login(data);}
    });
    return false;
}<commit_msg>Add setting change notification<commit_after>var coat = 36;
var jacket = 45;
var sweater = 60;
var profile;

function query(location) {
    $.ajax({
        type: 'GET',
        url: 'https://api.wunderground.com/api/dba29ab1640cac5b/hourly' + location + '.json',
        dataType: 'json',
        success: function(data) {processData(data);}
    });
}

function processData(data) {
    $('#searchResp').empty();
    $('#rec').show();
    var entries = data.hourly_forecast;
    var feels = entries[0].feelslike.english;
    var feelsMin = feels;
    var rain = false;
    var snow = false;
    for (var i = 1; i < 12; i++) {
        if (feelsMin > entries[i].feelslike.english) {
            feelsMin = entries[i].feelslike.english;
        }

        if (entries[i].condition.toLowerCase().includes("rain")) {
            rain = true;
        }
        if (entries[i].condition.toLowerCase().includes("snow")) {
            snow = true;
        }
    }

    var simp = 'No';
    var rec = 'You don\'t need a jacket';
    if (rain || snow) {
        simp = 'Yes';
    }
    if (snow) {
        rec = 'You should wear a coat today, it\'s snowing';
    } else if (rain) {
        if (feelsMin < coat) {
            rec = 'It\'s cold and raining out today, you need a coat';
        } else {
            rec = 'You should wear a jacket today, it\'s raining';
        }
    } else {
        if (feelsMin < coat) {
            simp = 'Yes';
            rec = 'It\'s cold out today, you need a coat';
        } else if (feelsMin < jacket) {
            simp = 'Yes';
            rec = 'You should wear a jacket today';
        } else if (feelsMin < sweater) {
            simp = 'No';
            rec = 'You only need a sweater';
        }
    }
    $('title').text('Do I Need a Jacket? ' + simp + '.');
    $('#simpResp').text(simp + '. ');
    $('#longResp').text(rec + '. The coldest it will feel today is ' + feelsMin + ' F.');
}

function search() {
    var query = $('#searchField').val();
    $('#rec').hide();
    $('body').append("");
    return false;
}

function processQuery(data) {
    var entries = data.RESULTS;
    $('#searchResp').empty();
    if (entries.length == 0) {
        $('#searchResp').append('No results.')
    } else {
        $('#searchResp').append('

');
    }
    for (var i = 0; i < entries.length; i++) {
        var entry = entries[i];
        $('#searchResp ul').append('
' + entry.name + '
')
    }
}

function onSignIn(googleUser) {
    profile = googleUser.getBasicProfile();
    $.ajax({
        type: 'GET',
        url: 'https://pantherman594.com/jacket.php?id=' + profile.getId(),
        success: function(data) {login(data);}
    });
}

function login(data) {
    $('#settings').show();
    if (data != "Error") {
        data = data.split(";");
        coat = data[0];
        jacket = data[1];
        sweater = data[2];
    }

    $('#coat').val(coat);
    $('#jacket').val(jacket);
    $('#sweater').val(sweater);
}

function settings() {
    if (!$('#coat').val()) $('#coat').val(coat);
    if (!$('#jacket').val()) $('#jacket').val(jacket);
    if (!$('#sweater').val()) $('#sweater').val(sweater);
    $.ajax({
        type: 'GET',
        url: 'https://pantherman594.com/jacket.php?id=' + profile.getId() + '&coat=' + $('#coat').val() + '&jacket=' + $('#jacket').val() + '&sweater=' + $('#sweater').val(),
        success: function(data) {login(data);}
    });
    alert("Settings changed!");
    return false;
}<|endoftext|>


<commit_before>'use strict';

var PLUGIN_NAME = 'gulp-watch-less';

var gulp = require('gulp'),
	gutil = require('gulp-util'),
	watch = require('gulp-watch'),
	mergeDefaults = require('lodash.defaults'),
	through = require('through2'),
	less = require('less');

function getLessFileImports(vinylFile, options, cb) {
	var imports = [];

	// Support (file, cb) signature.
	if(typeof options === 'function') {
		cb = options;
		options = null;
	}

	less.parse(
		vinylFile.contents.toString('utf8'),
		mergeDefaults(
			{
				filename: vinylFile.path
			},
			options || {}
		),
		function(err, root, imports, options) {
			// Add a better error message / properties.
			if (err) {
				err.lineNumber = err.line;
				err.fileName = err.filename;
				err.message = err.message + ' in file ' + err.fileName + ' line no. ' + err.lineNumber;
			}

			// Generate imports list from the files hash (sorted).
			var imports = Object.keys(imports.files).sort();

			cb(err, imports);
		}
	);
};

// Tracks watch streams e.g. `{filepath}: stream`.
var _streams = Object.create(null);

// Name of the event fired when @imports cause file to change. Overwrites the
// current file.event set by gulp-watch/gaze.
var changeEvent = 'changed:by:import';

function watchLessImports(file, options, cb, done) {
	var filePath = file.path,
		watchStream = _streams[filePath];

	getLessFileImports(file, options.less, function(err, imports) {
		var oldImports;

		if (err) { cb(new gutil.PluginError(PLUGIN_NAME, err)); }

		if(watchStream) {
			oldImports = watchStream._imports;

			// Check to ensure the @import arrays are identical.
			if(oldImports.length && oldImports.join() === imports.join()) {
				done();
				return;
			}

			// Clean up previous watch stream.
			watchStream.end();
			watchStream.unpipe();
			watchStream.close();
			delete _streams[filePath];
		}

		if(imports.length) {
			// Generate new watch stream.
			watchStream = _streams[filePath] = watch(imports, options, cb);

			// Expose @import list on the stream.
			watchStream._imports = imports;
		}

		done();
	});
}

module.exports = function (glob, options, callback) {
	if(!options) { options = {}; }
	if(!callback) { callback = function() {}; }

	options = mergeDefaults(options, {
		name: 'LESS',
		less: {}
	});

	var watchStream = watch(glob, options, callback)

	function watchImportStream(file, enc, cb) {
		var filePath = file.path;

		this.push(file);

		// Make sure we only execute the logic on external events and not when our
		// own internal changeEvent triggers it.
		if(file.event !== changeEvent) {
			watchLessImports(file, options, function(importFile) {
				watchStream._gaze.emit('all', changeEvent, filePath);
			},
			cb);
		} else { cb(); }
	}

	// Close all import watch streams when the watchStream ends.
	watchStream.on('end', function() { Object.keys(_streams).forEach(closeStream); });

	// Pipe the watch stream into the imports watcher so whenever any of the files
	// change, we re-generate our @import watcher so removals/additions are
	// detected.
	watchStream.pipe(through.obj(watchImportStream));

	// In order for the pipe to receive updates that the main less file changed
	// we must pipe the stream to itself.
	return watchStream.pipe(watchStream);
};
<commit_msg>Fix all jshint errors except for a possible use strict violation from using this within a function that isn't clearly a constructor or object instance.<commit_after>'use strict';

var PLUGIN_NAME = 'gulp-watch-less';

var gutil = require('gulp-util'),
	watch = require('gulp-watch'),
	mergeDefaults = require('lodash.defaults'),
	through = require('through2'),
	less = require('less');

function getLessFileImports(vinylFile, options, cb) {
	// Support (file, cb) signature.
	if(typeof options === 'function') {
		cb = options;
		options = null;
	}

	less.parse(
		vinylFile.contents.toString('utf8'),
		mergeDefaults(
			{
				filename: vinylFile.path
			},
			options || {}
		),
		function(err, root, imports, options) {
			// Add a better error message / properties.
			if (err) {
				err.lineNumber = err.line;
				err.fileName = err.filename;
				err.message = err.message + ' in file ' + err.fileName + ' line no. ' + err.lineNumber;
			}

			// Generate imports list from the files hash (sorted).
			cb(err, Object.keys(imports.files).sort());
		}
	);
}

// Tracks watch streams e.g. `{filepath}: stream`.
var _streams = Object.create(null);

// Name of the event fired when @imports cause file to change. Overwrites the
// current file.event set by gulp-watch/gaze.
var changeEvent = 'changed:by:import';

function watchLessImports(file, options, cb, done) {
	var filePath = file.path,
		watchStream = _streams[filePath];

	getLessFileImports(file, options.less, function(err, imports) {
		var oldImports;

		if (err) { cb(new gutil.PluginError(PLUGIN_NAME, err)); }

		if(watchStream) {
			oldImports = watchStream._imports;

			// Check to ensure the @import arrays are identical.
			if(oldImports.length && oldImports.join() === imports.join()) {
				done();
				return;
			}

			// Clean up previous watch stream.
			watchStream.end();
			watchStream.unpipe();
			watchStream.close();
			delete _streams[filePath];
		}

		if(imports.length) {
			// Generate new watch stream.
			watchStream = _streams[filePath] = watch(imports, options, cb);

			// Expose @import list on the stream.
			watchStream._imports = imports;
		}

		done();
	});
}

module.exports = function (glob, options, callback) {
	if(!options) { options = {}; }
	if(!callback) { callback = function() {}; }

	options = mergeDefaults(options, {
		name: 'LESS',
		less: {}
	});

	var watchStream = watch(glob, options, callback);

	function watchImportStream(file, enc, cb) {
		var filePath = file.path;

		this.push(file);

		// Make sure we only execute the logic on external events and not when our
		// own internal changeEvent triggers it.
		if(file.event !== changeEvent) {
			watchLessImports(file, options, function(importFile) {
				watchStream._gaze.emit('all', changeEvent, filePath);
			},
			cb);
		} else { cb(); }
	}

	// Close all import watch streams when the watchStream ends.
	// TODO `closeStream` is undefined. This should be defined to prevent runtime
	// errors and so that streams are properly closed.
	// watchStream.on('end', function() { Object.keys(_streams).forEach(closeStream); });

	// Pipe the watch stream into the imports watcher so whenever any of the files
	// change, we re-generate our @import watcher so removals/additions are
	// detected.
	watchStream.pipe(through.obj(watchImportStream));

	// In order for the pipe to receive updates that the main less file changed
	// we must pipe the stream to itself.
	return watchStream.pipe(watchStream);
};
<|endoftext|>


<commit_before>'use strict';

module.exports = plugin;

var path = require('path');
var gutil = require('gulp-util');
var through = require('through2');

var PLUGINNAME = 'gulp-rev-replace';

var REG_HTML = /([\s]href=|[\s]src=)['"][a-zA-Z_\-\d\.\/]+['"]|[\s:]url\(['"]?[a-zA-Z_\-\d\.\/]+['"]?\)/g;
var REG_SUB = /([\s]href=|[\s]src=)|['"]|[\s:]url\(|\)/g;

function plugin(options){
  var cache = [];
  var base;
  options = options || {};

  if(options.hasOwnProperty('base') && path.isAbsolute(options.base)){
    base = options.base.split(path.sep).join('/');
    console.log('\nbase => ' + base);
  }else{
    base = false;
  }

  var isLog = options.log || false;

  function log(msg){
    isLog && console.log(msg);
  }

  return through.obj(function(file, enc, cb){
    if(file.isNull()){
      this.push(file);
      return cb();
    }
    if(file.isStream()){
      this.emit('error', new gutil.PluginError(PLUGINNAME, 'Streaming not supported.'));
      return cb();
    }
    if(!base){
      this.emit('error', new gutil.PluginError(PLUGINNAME, 'param base required and must be absolute'));
      return cb();
    }
    cache.push(file);
    //this.push(file);
    cb();
  }, function(cb){
    var stream = this;
    var manifestObj = {};
    var HTML_MATCHED = 0;
    var REPLACED = 0;
    var DOMAIN = '/';

    if(options.manifest){
      options.manifest.on('data', function(file){
        manifestObj = JSON.parse(file.contents.toString());
      });
      options.manifest.on('end', replace);
    }

    if(options.prefix){
      DOMAIN = options.prefix.replace(/\/$/, '') + '/';
    }

    function replace(){

      cache.forEach(function(file){
        var fileContent = file.contents.toString();
        var filePath = path.dirname(file.history[0]);
        log('\nchecking file...' + file.history[0]);

        fileContent = fileContent.replace(REG_HTML, function(matchString){
          HTML_MATCHED++;
          var url = matchString.replace(REG_SUB, '');
          var absoluteUrl = '';
          var resultUrl = '';
          var result = '';
          if(url.indexOf('/') === 0){
            absoluteUrl = url.replace(/^\//, '');
          }else{
            absoluteUrl = path.join(filePath, url).split(path.sep).join('/').replace(base, '').replace(/^\//, '');
          }
          if(manifestObj[absoluteUrl]){
            resultUrl = DOMAIN + manifestObj[absoluteUrl];
            result = matchString.split(url).join(resultUrl);
            log(url + '  =>  ' + resultUrl);
            REPLACED++;
          }else{
            log('not find in manifest  =>  ' + matchString);
            result = matchString;
          }
          return result;
        });

        file.contents = new Buffer(fileContent);

        stream.push(file);

      });

      console.log('\nMATCHED:' + HTML_MATCHED + ' REPLACED:' + REPLACED + '\n');

      cb();

    }

  });
}
<commit_msg>[add] img url in {css,js} replaced as relative path<commit_after>'use strict';

module.exports = plugin;

var path = require('path');
var gutil = require('gulp-util');
var through = require('through2');

var PLUGINNAME = 'gulp-rev-replace';

var REG_HTML = /([\s]href=|[\s]src=)['"][a-zA-Z_\-\d\.\/]+['"]|[\s:]url\(['"]?[a-zA-Z_\-\d\.\/]+['"]?\)/g;
var REG_SUB = /([\s]href=|[\s]src=)|['"]|[\s:]url\(|\)/g;

function plugin(options){
  var cache = [];
  var base;
  options = options || {};

  if(options.hasOwnProperty('base') && path.isAbsolute(options.base)){
    base = options.base.split(path.sep).join('/');
    console.log('\nbase => ' + base);
  }else{
    base = false;
  }

  var isLog = options.log || false;

  function log(msg){
    isLog && console.log(msg);
  }

  return through.obj(function(file, enc, cb){
    if(file.isNull()){
      this.push(file);
      return cb();
    }
    if(file.isStream()){
      this.emit('error', new gutil.PluginError(PLUGINNAME, 'Streaming not supported.'));
      return cb();
    }
    if(!base){
      this.emit('error', new gutil.PluginError(PLUGINNAME, 'param base required and must be absolute'));
      return cb();
    }
    cache.push(file);
    //this.push(file);
    cb();
  }, function(cb){
    var stream = this;
    var manifestObj = {};
    var HTML_MATCHED = 0;
    var REPLACED = 0;
    var DOMAIN = '/';

    if(options.manifest){
      options.manifest.on('data', function(file){
        manifestObj = JSON.parse(file.contents.toString());
      });
      options.manifest.on('end', replace);
    }

    if(options.prefix){
      DOMAIN = options.prefix.replace(/\/$/, '') + '/';
    }

    function replace(){

      cache.forEach(function(file){
        var fileContent = file.contents.toString();
        var filePath = path.dirname(file.history[0]);
        var extname = path.extname(file.history[0]);
        log('\nchecking file...' + file.history[0]);

        fileContent = fileContent.replace(REG_HTML, function(matchString){
          HTML_MATCHED++;
          var url = matchString.replace(REG_SUB, '');
          var absoluteUrl = '';
          var resultUrl = '';
          var result = '';
          if(url.indexOf('/') === 0){
            absoluteUrl = url.replace(/^\//, '');
          }else{
            absoluteUrl = path.join(filePath, url).split(path.sep).join('/').replace(base, '').replace(/^\//, '');
          }
          if(manifestObj[absoluteUrl]){
            if(extname == '.js' || extname == '.css'){
              resultUrl = path.relative(filePath, path.join(base,manifestObj[absoluteUrl])).split(path.sep).join('/');
            }else{
              resultUrl = DOMAIN + manifestObj[absoluteUrl];
            }

            result = matchString.split(url).join(resultUrl);
            log(url + '  =>  ' + resultUrl);
            REPLACED++;
          }else{
            log('not find in manifest  =>  ' + matchString);
            result = matchString;
          }
          return result;
        });

        file.contents = new Buffer(fileContent);

        stream.push(file);

      });

      console.log('\nMATCHED:' + HTML_MATCHED + ' REPLACED:' + REPLACED + '\n');

      cb();

    }

  });
}
<|endoftext|>


<commit_before>'use strict';

const Metalsmith   = require('metalsmith')
const jade         = require('metalsmith-jade')
const sass         = require('metalsmith-sass')
const browserSync  = require('metalsmith-browser-sync')
const markdown     = require('metalsmith-markdown')
const layouts      = require('metalsmith-layouts')
const permalinks   = require('metalsmith-permalinks')
const babel        = require('metalsmith-babel')
const bourbon      = require('node-bourbon')
const path         = require('path')
const postcss      = require('metalsmith-postcss')
const autoprefixer = require('autoprefixer')
const copy         = require('metalsmith-copy')
const each         = require('metalsmith-each')
const navigation   = require('metalsmith-navigation')
const changed      = require('metalsmith-changed')
const lunr         = require('metalsmith-lunr')
const lunr_        = require('lunr')

// --- general build settings --- //
const docsVersion = 'latest';

const updatePaths = function(file, filename) {
  if (path.basename(filename) === "index.html" ) { return filename; }

  if (path.extname(filename) === '.html' &&
      path.extname(filename) !== '' &&
      filename.substr(0, 17) !== 'get-started-docs/' &&
      process.env.CI) {
    console.log(`Change filename ${filename} to ${filename.substr(0, filename.length-5)}`);
    return filename = filename.substr(0, filename.length-5);
  }
  return filename;
};

// --------- figuring out the navigation ---------- //
const navConfig = {
    header: {
      includeDirs: true,
      // pathProperty: docsVersion,
      pathProperty: 'nav_path',
      childrenProperty: 'nav_children',
    }
}

const navSettings = {
  navListProperty: 'navs',
  permalinks: false,
}

let nav = navigation(navConfig, navSettings);

// --------- Compiling the Markdown files to HTML --------//
let createDocs = function (event, file) {
  Metalsmith(path.join(__dirname, 'dcos-docs'))
    .source(docsVersion)
    .use(markdown({
      smartypants: true,
      gfm: true,
      tables: true
    }))
    .use(nav)
    .use(layouts({
      engine: 'jade',
      directory: path.join('..', 'layouts'),
      default: 'docs.jade',
    }))
    .use(jade({
      pretty: true
    }))
    .clean(false)
    .use(changed({
      extnames: {
        '.md': '.html' // build if src/file.md is newer than build/file.html
      }
    }))
    .use(each(updatePaths))
    .use(lunr({
      indexPath: 'lunr.json',
      fields: {
        contents: 1,
        tags: 10
      },
      pipelineFunctions: [
        lunr_.trimmer
      ]
    }))
    .destination(path.join('..', 'build', 'docs', docsVersion))
    .build((err) => {
      if (err) throw err
    })
}

Metalsmith(__dirname)
  .use(markdown({
    smartypants: true,
    gfm: true,
    tables: true
  }))
  .use(jade({
    pretty: true
  }))
  .use(sass({
    outputStyle: 'expanded',
    includePaths: [
      '/node_modules/',
      path.join(__dirname, 'node_modules/support-for/sass')
     ].concat(bourbon.includePaths)
  }))
  .use(postcss([
    autoprefixer({ browsers: ['last 4 versions'] })
  ]))
  .use(babel({
    presets: ['es2015'],
    only: '/src/scripts/**/*'
  }))
  .use(copy({
    pattern: 'assets/*',
    directory: 'assets'
  }))
  .use(each(updatePaths))
  .clean(false)
  .use(changed())
  .use((() => {
    if(!process.env.CI) {
      return browserSync({
        server: {
          baseDir: './build',
          middleware: function(req, res, next) {
            var file = `./build${req.originalUrl}.html`;
            require('fs').exists(file, function(exists) {
              if (exists) req.url += '.html';
              next();
            });
          }
        },
        files: ['./src/**/*', './dcos-docs/**/*', './layouts/**/*', './mixins/**/*']
      }, null, createDocs);
    }
  })())
  .build((err) => {
    if (err) throw err
  })

createDocs();
<commit_msg>Fix broken babel option<commit_after>'use strict';

const Metalsmith   = require('metalsmith')
const jade         = require('metalsmith-jade')
const sass         = require('metalsmith-sass')
const browserSync  = require('metalsmith-browser-sync')
const markdown     = require('metalsmith-markdown')
const layouts      = require('metalsmith-layouts')
const permalinks   = require('metalsmith-permalinks')
const babel        = require('metalsmith-babel')
const bourbon      = require('node-bourbon')
const path         = require('path')
const postcss      = require('metalsmith-postcss')
const autoprefixer = require('autoprefixer')
const copy         = require('metalsmith-copy')
const each         = require('metalsmith-each')
const navigation   = require('metalsmith-navigation')
const changed      = require('metalsmith-changed')
const lunr         = require('metalsmith-lunr')
const lunr_        = require('lunr')

// --- general build settings --- //
const docsVersion = 'latest';

const updatePaths = function(file, filename) {
  if (path.basename(filename) === "index.html" ) { return filename; }

  if (path.extname(filename) === '.html' &&
      path.extname(filename) !== '' &&
      filename.substr(0, 17) !== 'get-started-docs/' &&
      process.env.CI) {
    console.log(`Change filename ${filename} to ${filename.substr(0, filename.length-5)}`);
    return filename = filename.substr(0, filename.length-5);
  }
  return filename;
};

// --------- figuring out the navigation ---------- //
const navConfig = {
    header: {
      includeDirs: true,
      // pathProperty: docsVersion,
      pathProperty: 'nav_path',
      childrenProperty: 'nav_children',
    }
}

const navSettings = {
  navListProperty: 'navs',
  permalinks: false,
}

let nav = navigation(navConfig, navSettings);

// --------- Compiling the Markdown files to HTML --------//
let createDocs = function (event, file) {
  Metalsmith(path.join(__dirname, 'dcos-docs'))
    .source(docsVersion)
    .use(markdown({
      smartypants: true,
      gfm: true,
      tables: true
    }))
    .use(nav)
    .use(layouts({
      engine: 'jade',
      directory: path.join('..', 'layouts'),
      default: 'docs.jade',
    }))
    .use(jade({
      pretty: true
    }))
    .clean(false)
    .use(changed({
      extnames: {
        '.md': '.html' // build if src/file.md is newer than build/file.html
      }
    }))
    .use(each(updatePaths))
    .use(lunr({
      indexPath: 'lunr.json',
      fields: {
        contents: 1,
        tags: 10
      },
      pipelineFunctions: [
        lunr_.trimmer
      ]
    }))
    .destination(path.join('..', 'build', 'docs', docsVersion))
    .build((err) => {
      if (err) throw err
    })
}

Metalsmith(__dirname)
  .use(markdown({
    smartypants: true,
    gfm: true,
    tables: true
  }))
  .use(jade({
    pretty: true
  }))
  .use(sass({
    outputStyle: 'expanded',
    includePaths: [
      '/node_modules/',
      path.join(__dirname, 'node_modules/support-for/sass')
     ].concat(bourbon.includePaths)
  }))
  .use(postcss([
    autoprefixer({ browsers: ['last 4 versions'] })
  ]))
  .use(babel({
    presets: ['es2015'],
    only: './src/scripts/**'
  }))
  .use(copy({
    pattern: 'assets/*',
    directory: 'assets'
  }))
  .use(each(updatePaths))
  .clean(false)
  .use(changed())
  .use((() => {
    if(!process.env.CI) {
      return browserSync({
        server: {
          baseDir: './build',
          middleware: function(req, res, next) {
            var file = `./build${req.originalUrl}.html`;
            require('fs').exists(file, function(exists) {
              if (exists) req.url += '.html';
              next();
            });
          }
        },
        files: ['./src/**/*', './dcos-docs/**/*', './layouts/**/*', './mixins/**/*']
      }, null, createDocs);
    }
  })())
  .build((err) => {
    if (err) throw err
  })

createDocs();
<|endoftext|>


<commit_before>'use strict';

var Separator = require('choices-separator');
var Choice = require('./lib/choice');
var utils = require('./lib/utils');

/**
 * Choices collection
 * Collection of multiple `choice` objects
 * @param {Array} choices  All `choice` to keep in the collection
 */

function Choices(choices, answers) {
  this.choices = [];
  this.keymap = {};

  choices = utils.arrayify(choices);
  var len = choices.length;
  var idx = -1;

  while (++idx < len) {
    var choice = choices[idx];
    if (choice.type === 'separator') {
      if (!choice.isSeparator) {
        choice = new Separator(choice.line);
      }
    } else {
      choice = new Choice(choice, answers);
      this.keymap[choice.key] = choice;
    }
    this.choices.push(choice);
  }
};

/**
 * Get a valid choice from the collection
 * @param  {Number} `idx` The selected choice index
 * @return {Object|undefined} Return the matched choice object or undefined
 */

Choices.prototype.getChoice = function(idx) {
  if (utils.isNumber(idx) && idx !== ' ') {
    return this.realChoices[idx];
  }
  if (typeof idx === 'string') {
    return this.keymap[idx];
  }
};

/**
 * Get a valid choice from the collection
 * @param  {Number} idx The selected choice index
 * @return {Choice|Undefined} Return the matched choice or undefined
 */

Choices.prototype.getIndex = function(key) {
  if (utils.isNumber(key) && key !== -1 && key < this.realLength) {
    return key;
  }
  if (typeof key === 'string') {
    return this.pluck('value').indexOf(key);
  }
  return 0;
};

/**
 * Get a raw element from the collection
 * @param  {Number} idx  The selected index value
 * @return {Choice|Undefined} Return the matched choice or undefined
 */

Choices.prototype.get = function(idx) {
  if (!utils.isNumber(idx)) {
    throw new TypeError('expected index to be a number');
  }
  return this.choices[idx];
};

/**
 * Match the valid choices against a where clause
 * @param  {Object} whereClause Lodash `where` clause
 * @return {Array}              Matching choices or empty array
 */

Choices.prototype.where = function(val) {
  return this.realChoices.filter(function(choice) {
    if (typeof val === 'function') {
      return val(choice);
    }
    if (typeof val === 'string') {
      return !!choice[val];
    }

    if (utils.isObject(val)) {
      for (var key in val) {
        if (!choice.hasOwnProperty(key)) {
          return false;
        }
        if (val[key] !== choice[key]) {
          return false;
        }
      }
    }
    return true;
  });
};

/**
 * Pluck a particular key from the choices
 * @param  {String} propertyName Property name to select
 * @return {Array}               Selected properties
 */

Choices.prototype.pluck = function(key) {
  return this.realChoices.map(function(choice) {
    return choice[key];
  });
};

// Expose usual Array methods
Choices.prototype.indexOf = function() {
  return this.choices.indexOf.apply(this.choices, arguments);
};

Choices.prototype.forEach = function() {
  return this.choices.forEach.apply(this.choices, arguments);
};

Choices.prototype.filter = function() {
  return this.choices.filter.apply(this.choices, arguments);
};

Choices.prototype.push = function() {
  var choices = utils.flatten([].slice.call(arguments));
  var len = choices.length;
  var idx = -1;

  while (++idx < len) {
    var choice = choices[idx];
    this.choices.push(new Choice(choice));
    if (choice.type !== 'separator') {
      this.realChoices.push(choice);
    }
  }
  return this.choices;
};

Object.defineProperty(Choices.prototype, 'realChoices', {
  set: function() {
    throw new Error('.realChoices is a getter and cannot be defined');
  },
  get: function() {
    var choices = [];
    var idx = -1;
    while (++idx < this.length) {
      var choice = this.choices[idx];
      if (choice.type !== 'separator' && !choice.disabled) {
        choices.push(choice);
      }
    }
    return choices;
  }
});

Object.defineProperty(Choices.prototype, 'length', {
  set: function() {
    throw new Error('.length is a getter and cannot be defined');
  },
  get: function() {
    return this.choices.length;
  }
});

Object.defineProperty(Choices.prototype, 'realLength', {
  set: function() {
    throw new Error('.realLength is a getter and cannot be defined');
  },
  get: function() {
    return this.realChoices.length;
  }
});

Object.defineProperty(Choices.prototype, 'all', {
  get: function() {
    return this.choices;
  }
});

module.exports = Choices;
<commit_msg>adds `.toggle` method<commit_after>'use strict';

var Separator = require('choices-separator');
var Choice = require('./lib/choice');
var utils = require('./lib/utils');

/**
 * Choices collection
 * Collection of multiple `choice` objects
 * @param {Array} choices  All `choice` to keep in the collection
 */

function Choices(choices, answers) {
  this.choices = [];
  this.keymap = {};

  choices = utils.arrayify(choices);
  var len = choices.length;
  var idx = -1;

  while (++idx < len) {
    var choice = choices[idx];
    if (choice.type === 'separator') {
      if (!choice.isSeparator) {
        choice = new Separator(choice.line);
      }
    } else {
      choice = new Choice(choice, answers);
      this.keymap[choice.key] = choice;
    }
    this.choices.push(choice);
  }
};

/**
 * Get a valid choice from the collection
 * @param  {Number} `idx` The selected choice index
 * @return {Object|undefined} Return the matched choice object or undefined
 */

Choices.prototype.getChoice = function(idx) {
  if (utils.isNumber(idx) && idx !== ' ') {
    return this.realChoices[idx];
  }
  if (typeof idx === 'string') {
    return this.keymap[idx];
  }
};

/**
 * Get a valid choice from the collection
 * @param  {Number} idx The selected choice index
 * @return {Choice|Undefined} Return the matched choice or undefined
 */

Choices.prototype.getIndex = function(key) {
  if (utils.isNumber(key) && key !== -1 && key < this.realLength) {
    return key;
  }
  if (typeof key === 'string') {
    return this.pluck('value').indexOf(key);
  }
  return 0;
};

/**
 * Get a raw element from the collection
 * @param  {Number} idx  The selected index value
 * @return {Choice|Undefined} Return the matched choice or undefined
 */

Choices.prototype.get = function(idx) {
  if (!utils.isNumber(idx)) {
    throw new TypeError('expected index to be a number');
  }
  return this.choices[idx];
};

/**
 * Toggle the choice at the given `idx`.
 * @param  {Number} `idx`  The index of the choice to toggle.
 */

Choices.prototype.toggle = function(idx) {
  var checked = this.getChoice(idx).checked;
  this.getChoice(idx).checked = !checked;
};

/**
 * Match the valid choices against a where clause
 * @param  {Object} whereClause Lodash `where` clause
 * @return {Array}              Matching choices or empty array
 */

Choices.prototype.where = function(val) {
  return this.realChoices.filter(function(choice) {
    if (typeof val === 'function') {
      return val(choice);
    }
    if (typeof val === 'string') {
      return !!choice[val];
    }

    if (utils.isObject(val)) {
      for (var key in val) {
        if (!choice.hasOwnProperty(key)) {
          return false;
        }
        if (val[key] !== choice[key]) {
          return false;
        }
      }
    }
    return true;
  });
};

/**
 * Pluck a particular key from the choices
 * @param  {String} propertyName Property name to select
 * @return {Array}               Selected properties
 */

Choices.prototype.pluck = function(key) {
  return this.realChoices.map(function(choice) {
    return choice[key];
  });
};

// Expose usual Array methods
Choices.prototype.indexOf = function() {
  return this.choices.indexOf.apply(this.choices, arguments);
};

Choices.prototype.forEach = function() {
  return this.choices.forEach.apply(this.choices, arguments);
};

Choices.prototype.filter = function() {
  return this.choices.filter.apply(this.choices, arguments);
};

Choices.prototype.push = function() {
  var choices = utils.flatten([].slice.call(arguments));
  var len = choices.length;
  var idx = -1;

  while (++idx < len) {
    var choice = choices[idx];
    this.choices.push(new Choice(choice));
    if (choice.type !== 'separator') {
      this.realChoices.push(choice);
    }
  }
  return this.choices;
};

Object.defineProperty(Choices.prototype, 'realChoices', {
  set: function() {
    throw new Error('.realChoices is a getter and cannot be defined');
  },
  get: function() {
    var choices = [];
    var idx = -1;
    while (++idx < this.length) {
      var choice = this.choices[idx];
      if (choice.type !== 'separator' && !choice.disabled) {
        choices.push(choice);
      }
    }
    return choices;
  }
});

Object.defineProperty(Choices.prototype, 'length', {
  set: function() {
    throw new Error('.length is a getter and cannot be defined');
  },
  get: function() {
    return this.choices.length;
  }
});

Object.defineProperty(Choices.prototype, 'realLength', {
  set: function() {
    throw new Error('.realLength is a getter and cannot be defined');
  },
  get: function() {
    return this.realChoices.length;
  }
});

Object.defineProperty(Choices.prototype, 'all', {
  get: function() {
    return this.choices;
  }
});

module.exports = Choices;
<|endoftext|>