Code coverage report for yeoman-generator/lib/util/conflicter.js

Statements: 75.31% (61 / 81)      Branches: 73.91% (17 / 23)      Functions: 66.67% (14 / 21)      Lines: 75.31% (61 / 81)      Ignored: none     

All files » yeoman-generator/lib/util/ » conflicter.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180    1 1 1 1 1 1   1 105 105 105     1   1 42 3           42       42       42 42     1 41 41     1       1       1 42 41 40       40 40               42 41         41 41       1       2 2   2                                                                                     2 2 2     2 2 2         1 41 41 34 34     7 6 6 5     6             6   6 6       1         1 1       1 1    
'use strict';
 
var fs = require('fs');
var path = require('path');
var events = require('events');
var async = require('async');
var isBinaryFile = require('isbinaryfile');
var util = require('util');
 
var Conflicter = module.exports = function Conflicter(adapter) {
  events.EventEmitter.call(this);
  this.adapter = adapter;
  this.conflicts = [];
};
 
util.inherits(Conflicter, events.EventEmitter);
 
Conflicter.prototype.add = function add(conflict) {
  if (typeof conflict === 'string') {
    conflict = {
      file: conflict,
      content: fs.readFileSync(conflict, 'utf8')
    };
  }
 
  Iif (!conflict.file) {
    throw new Error('Missing conflict.file option');
  }
 
  Iif (conflict.content === undefined) {
    throw new Error('Missing conflict.content option');
  }
 
  this.conflicts.push(conflict);
  return this;
};
 
Conflicter.prototype.reset = function reset() {
  this.conflicts = [];
  return this;
};
 
Conflicter.prototype.pop = function pop() {
  return this.conflicts.pop();
};
 
Conflicter.prototype.shift = function shift() {
  return this.conflicts.shift();
};
 
Conflicter.prototype.resolve = function resolve(cb) {
  var resolveConflicts = function (conflict) {
    return function (next) {
      Iif (!conflict) {
        return next();
      }
 
      this.collision(conflict.file, conflict.content, function (status) {
        this.emit('resolved:' + conflict.file, {
          status: status,
          callback: next
        });
      }.bind(this));
    }.bind(this);
  }.bind(this);
 
  async.series(this.conflicts.map(resolveConflicts), function (err) {
    Iif (err) {
      cb();
      return this.emit('error', err);
    }
 
    this.reset();
    cb();
  }.bind(this));
};
 
Conflicter.prototype._ask = function (filepath, content, cb) {
  // for this particular use case, might use prompt module directly to avoid
  // the additional "Are you sure?" prompt
 
  var self = this;
  var rfilepath = path.relative(process.cwd(), path.resolve(filepath));
 
  var config = [{
    type: 'expand',
    message: 'Overwrite ' + rfilepath + '?',
    choices: [{
      key: 'y',
      name: 'overwrite',
      value: function (cb) {
        self.adapter.log.force(rfilepath);
        return cb('force');
      }
    }, {
      key: 'n',
      name: 'do not overwrite',
      value: function (cb) {
        self.adapter.log.skip(rfilepath);
        return cb('skip');
      }
    }, {
      key: 'a',
      name: 'overwrite this and all others',
      value: function (cb) {
        self.adapter.log.force(rfilepath);
        self.force = true;
        return cb('force');
      }
    }, {
      key: 'x',
      name: 'abort',
      value: function (cb) {
        self.adapter.log.writeln('Aborting ...');
        return process.exit(0);
      }
    }, {
      key: 'd',
      name: 'show the differences between the old and the new',
      value: function (cb) {
        self.diff(fs.readFileSync(filepath, 'utf8'), content);
        return self._ask(filepath, content, cb);
      }
    }],
    name: 'overwrite'
  }];
 
  process.nextTick(function () {
    this.emit('prompt', config);
    this.emit('conflict', filepath);
  }.bind(this));
 
  this.adapter.prompt(config, function (result) {
    result.overwrite(function (action) {
      cb(action);
    });
  });
};
 
Conflicter.prototype.collision = function collision(filepath, content, cb) {
  var rfilepath = path.relative(process.cwd(), path.resolve(filepath));
  if (!fs.existsSync(filepath)) {
    this.adapter.log.create(rfilepath);
    return cb('create');
  }
 
  if (!fs.statSync(path.resolve(filepath)).isDirectory()) {
    var encoding = null;
    if (!isBinaryFile(path.resolve(filepath))) {
      encoding = 'utf8';
    }
 
    var actual = fs.readFileSync(path.resolve(filepath), encoding);
 
    // In case of binary content, `actual` and `content` are `Buffer` objects,
    // we just can't compare those 2 objects with standard `===`,
    // so we convert each binary content to an hexadecimal string first, and then compare them with standard `===`
    //
    // For not binary content, we can directly compare the 2 strings this way
    Eif ((!encoding && (actual.toString('hex') === content.toString('hex'))) ||
      (actual === content)) {
      this.adapter.log.identical(rfilepath);
      return cb('identical');
    }
  }
 
  Iif (this.force) {
    this.adapter.log.force(rfilepath);
    return cb('force');
  }
 
  this.adapter.log.conflict(rfilepath);
  this._ask(filepath, content, cb);
};
 
// below is borrowed code from visionmedia's excellent mocha (and its reporter)
Conflicter.prototype.diff = function _diff(actual, expected) {
  return this.adapter.diff(actual, expected);
};