Repository: ember-cli/ember-cli
Pull request #5931 (2016-05-25 19:54:08)

Extract standalone insertIntoFile() function
This PR extracts the `insertIntoFile()` method from the `Blueprint` class into its own utility file to reduce the complexity of the class.

Ultimately we might want to extract this function into its own project and deprecate its use through the `Blueprint` class but this seems like a good first step for now.

/cc @trabus


Patch:
diff --git a/lib/models/blueprint.js b/lib/models/blueprint.js
index fb37609a8..e0cf2dfc9 100644
--- a/lib/models/blueprint.js
+++ b/lib/models/blueprint.js
@@ -10,6 +10,7 @@ var MarkdownColor       = require('../utilities/markdown-color');
 var printableProperties = require('../utilities/printable-properties').blueprint;
 var sequence            = require('../utilities/sequence');
 var printCommand        = require('../utilities/print-command');
+var insertIntoFile      = require('../utilities/insert-into-file');
 var fs                  = require('fs-extra');
 var existsSync          = require('exists-sync');
 var inflector           = require('inflection');
@@ -1161,60 +1162,8 @@ var Blueprint = CoreObject.extend({
     @return {Promise}
   */
   insertIntoFile: function(pathRelativeToProjectRoot, contentsToInsert, providedOptions) {
-    var fullPath          = path.join(this.project.root, pathRelativeToProjectRoot);
-    var originalContents  = '';
-
-    if (existsSync(fullPath)) {
-      originalContents = fs.readFileSync(fullPath, { encoding: 'utf8' });
-    }
-
-    var contentsToWrite   = originalContents;
-
-    var options           = providedOptions || {};
-    var alreadyPresent    = originalContents.indexOf(contentsToInsert) > -1;
-    var insert            = !alreadyPresent;
-    var insertBehavior    = 'end';
-
-    if (options.before) { insertBehavior = 'before'; }
-    if (options.after)  { insertBehavior = 'after'; }
-
-    if (options.force) { insert = true; }
-
-    if (insert) {
-      if (insertBehavior === 'end') {
-        contentsToWrite += contentsToInsert;
-      } else {
-        var contentMarker      = options[insertBehavior];
-        var contentMarkerIndex = contentsToWrite.indexOf(contentMarker);
-
-        if (contentMarkerIndex !== -1) {
-          var insertIndex = contentMarkerIndex;
-          if (insertBehavior === 'after') { insertIndex += contentMarker.length; }
-
-          contentsToWrite = contentsToWrite.slice(0, insertIndex) +
-                            contentsToInsert + EOL +
-                            contentsToWrite.slice(insertIndex);
-        }
-      }
-    }
-
-    var returnValue = {
-      path: fullPath,
-      originalContents: originalContents,
-      contents: contentsToWrite,
-      inserted: false
-    };
-
-    if (contentsToWrite !== originalContents) {
-      returnValue.inserted = true;
-
-      return writeFile(fullPath, contentsToWrite)
-        .then(function() {
-          return returnValue;
-        });
-    } else {
-      return Promise.resolve(returnValue);
-    }
+    var fullPath = path.join(this.project.root, pathRelativeToProjectRoot);
+    return insertIntoFile(fullPath, contentsToInsert, providedOptions);
   },
 
   _printCommand: printCommand,
diff --git a/lib/utilities/insert-into-file.js b/lib/utilities/insert-into-file.js
new file mode 100644
index 000000000..093744baa
--- /dev/null
+++ b/lib/utilities/insert-into-file.js
@@ -0,0 +1,107 @@
+'use strict';
+
+var fs = require('fs-extra');
+var existsSync = require('exists-sync');
+var EOL = require('os').EOL;
+var Promise = require('../ext/promise');
+
+var writeFile = Promise.denodeify(fs.outputFile);
+
+/**
+  Inserts the given content into a file. If the `contentsToInsert` string is already
+  present in the current contents, the file will not be changed unless `force` option
+  is passed.
+
+  If `options.before` is specified, `contentsToInsert` will be inserted before
+  the first instance of that string.  If `options.after` is specified, the
+  contents will be inserted after the first instance of that string.
+  If the string specified by options.before or options.after is not in the file,
+  no change will be made.
+
+  If neither `options.before` nor `options.after` are present, `contentsToInsert`
+  will be inserted at the end of the file.
+
+  Example:
+
+  ```
+  // app/router.js
+  Router.map(function() {
+  });
+  ```
+
+  ```
+  insertIntoFile('app/router.js', '  this.route("admin");', {
+    after: 'Router.map(function() {' + EOL
+  });
+  ```
+
+  ```
+  // app/router.js
+  Router.map(function() {
+    this.route("admin");
+  });
+  ```
+
+  @method insertIntoFile
+  @param {String} pathRelativeToProjectRoot
+  @param {String} contentsToInsert
+  @param {Object} providedOptions
+  @return {Promise}
+*/
+function insertIntoFile(fullPath, contentsToInsert, providedOptions) {
+  var originalContents  = '';
+
+  if (existsSync(fullPath)) {
+    originalContents = fs.readFileSync(fullPath, { encoding: 'utf8' });
+  }
+
+  var contentsToWrite   = originalContents;
+
+  var options           = providedOptions || {};
+  var alreadyPresent    = originalContents.indexOf(contentsToInsert) > -1;
+  var insert            = !alreadyPresent;
+  var insertBehavior    = 'end';
+
+  if (options.before) { insertBehavior = 'before'; }
+  if (options.after)  { insertBehavior = 'after'; }
+
+  if (options.force) { insert = true; }
+
+  if (insert) {
+    if (insertBehavior === 'end') {
+      contentsToWrite += contentsToInsert;
+    } else {
+      var contentMarker      = options[insertBehavior];
+      var contentMarkerIndex = contentsToWrite.indexOf(contentMarker);
+
+      if (contentMarkerIndex !== -1) {
+        var insertIndex = contentMarkerIndex;
+        if (insertBehavior === 'after') { insertIndex += contentMarker.length; }
+
+        contentsToWrite = contentsToWrite.slice(0, insertIndex) +
+          contentsToInsert + EOL +
+          contentsToWrite.slice(insertIndex);
+      }
+    }
+  }
+
+  var returnValue = {
+    path: fullPath,
+    originalContents: originalContents,
+    contents: contentsToWrite,
+    inserted: false
+  };
+
+  if (contentsToWrite !== originalContents) {
+    returnValue.inserted = true;
+
+    return writeFile(fullPath, contentsToWrite).then(function() {
+      return returnValue;
+    });
+
+  } else {
+    return Promise.resolve(returnValue);
+  }
+}
+
+module.exports = insertIntoFile;


Test patch:
diff --git a/tests/unit/models/blueprint-test.js b/tests/unit/models/blueprint-test.js
index 649d89e27..5b441c2e1 100644
--- a/tests/unit/models/blueprint-test.js
+++ b/tests/unit/models/blueprint-test.js
@@ -1598,212 +1598,6 @@ help in detail');
     });
   });
 
-  describe('insertIntoFile', function() {
-    var blueprint;
-    var ui;
-    var tmpdir;
-    var project;
-    var filename;
-
-    beforeEach(function() {
-      return mkTmpDirIn(tmproot).then(function(dir) {
-        tmpdir = dir;
-        blueprint = new Blueprint(basicBlueprint);
-        ui        = new MockUI();
-        project   = new MockProject();
-        // normally provided by `install`, but mocked here for testing
-        project.root = tmpdir;
-        blueprint.project = project;
-        filename = 'foo-bar-baz.txt';
-      });
-
-    });
-
-    afterEach(function() {
-      return remove(tmproot);
-    });
-
-    it('will create the file if not already existing', function() {
-      var toInsert = 'blahzorz blammo';
-
-      return blueprint.insertIntoFile(filename, toInsert)
-        .then(function(result) {
-          var contents = fs.readFileSync(path.join(project.root, filename), { encoding: 'utf8' });
-
-          expect(contents).to.contain(toInsert);
-          expect(result.originalContents).to.equal('', 'returned object should contain original contents');
-          expect(result.inserted).to.equal(true, 'inserted should indicate that the file was modified');
-          expect(contents).to.equal(result.contents, 'returned object should contain contents');
-        });
-    });
-
-    it('will insert into the file if it already exists', function() {
-      var toInsert = 'blahzorz blammo';
-      var originalContent = 'some original content\n';
-      var filePath = path.join(project.root, filename);
-
-      fs.writeFileSync(filePath, originalContent, { encoding: 'utf8' });
-
-      return blueprint.insertIntoFile(filename, toInsert)
-        .then(function(result) {
-          var contents = fs.readFileSync(path.join(project.root, filename), { encoding: 'utf8' });
-
-          expect(contents).to.equal(originalContent + toInsert, 'inserted contents should be appended to original');
-          expect(result.originalContents).to.equal(originalContent, 'returned object should contain original contents');
-          expect(result.inserted).to.equal(true, 'inserted should indicate that the file was modified');
-        });
-    });
-
-    it('will not insert into the file if it already contains the content', function() {
-      var toInsert = 'blahzorz blammo';
-      var filePath = path.join(project.root, filename);
-
-      fs.writeFileSync(filePath, toInsert, { encoding: 'utf8' });
-
-      return blueprint.insertIntoFile(filename, toInsert)
-        .then(function(result) {
-          var contents = fs.readFileSync(path.join(project.root, filename), { encoding: 'utf8' });
-
-          expect(contents).to.equal(toInsert, 'contents should be unchanged');
-          expect(result.inserted).to.equal(false, 'inserted should indicate that the file was not modified');
-        });
-    });
-
-    it('will insert into the file if it already contains the content if force option is passed', function() {
-      var toInsert = 'blahzorz blammo';
-      var filePath = path.join(project.root, filename);
-
-      fs.writeFileSync(filePath, toInsert, { encoding: 'utf8' });
-
-      return blueprint.insertIntoFile(filename, toInsert, { force: true })
-        .then(function(result) {
-          var contents = fs.readFileSync(path.join(project.root, filename), { encoding: 'utf8' });
-
-          expect(contents).to.equal(toInsert + toInsert, 'contents should be unchanged');
-          expect(result.inserted).to.equal(true, 'inserted should indicate that the file was not modified');
-        });
-    });
-
-    it('will insert into the file after a specified string if options.after is specified', function() {
-      var toInsert = 'blahzorz blammo';
-      var line1 = 'line1 is here';
-      var line2 = 'line2 here';
-      var line3 = 'line3';
-      var originalContent = [line1, line2, line3].join(EOL);
-      var filePath = path.join(project.root, filename);
-
-      fs.writeFileSync(filePath, originalContent, { encoding: 'utf8' });
-
-      return blueprint.insertIntoFile(filename, toInsert, {after: line2 + EOL})
-        .then(function(result) {
-          var contents = fs.readFileSync(path.join(project.root, filename), { encoding: 'utf8' });
-
-          expect(contents).to.equal([line1, line2, toInsert, line3].join(EOL),
-                       'inserted contents should be inserted after the `after` value');
-          expect(result.originalContents).to.equal(originalContent, 'returned object should contain original contents');
-          expect(result.inserted).to.equal(true, 'inserted should indicate that the file was modified');
-        });
-    });
-
-    it('will insert into the file after the first instance of options.after only', function() {
-      var toInsert = 'blahzorz blammo';
-      var line1 = 'line1 is here';
-      var line2 = 'line2 here';
-      var line3 = 'line3';
-      var originalContent = [line1, line2, line2, line3].join(EOL);
-      var filePath = path.join(project.root, filename);
-
-      fs.writeFileSync(filePath, originalContent, { encoding: 'utf8' });
-
-      return blueprint.insertIntoFile(filename, toInsert, {after: line2 + EOL})
-        .then(function(result) {
-          var contents = fs.readFileSync(path.join(project.root, filename), { encoding: 'utf8' });
-
-          expect(contents).to.equal([line1, line2, toInsert, line2, line3].join(EOL),
-                       'inserted contents should be inserted after the `after` value');
-          expect(result.originalContents).to.equal(originalContent, 'returned object should contain original contents');
-          expect(result.inserted).to.equal(true, 'inserted should indicate that the file was modified');
-        });
-    });
-
-    it('will insert into the file before a specified string if options.before is specified', function() {
-      var toInsert = 'blahzorz blammo';
-      var line1 = 'line1 is here';
-      var line2 = 'line2 here';
-      var line3 = 'line3';
-      var originalContent = [line1, line2, line3].join(EOL);
-      var filePath = path.join(project.root, filename);
-
-      fs.writeFileSync(filePath, originalContent, { encoding: 'utf8' });
-
-      return blueprint.insertIntoFile(filename, toInsert, {before: line2 + EOL})
-        .then(function(result) {
-          var contents = fs.readFileSync(path.join(project.root, filename), { encoding: 'utf8' });
-
-          expect(contents).to.equal([line1, toInsert, line2, line3].join(EOL),
-                       'inserted contents should be inserted before the `before` value');
-          expect(result.originalContents).to.equal(originalContent, 'returned object should contain original contents');
-          expect(result.inserted).to.equal(true, 'inserted should indicate that the file was modified');
-        });
-    });
-
-    it('will insert into the file before the first instance of options.before only', function() {
-      var toInsert = 'blahzorz blammo';
-      var line1 = 'line1 is here';
-      var line2 = 'line2 here';
-      var line3 = 'line3';
-      var originalContent = [line1, line2, line2, line3].join(EOL);
-      var filePath = path.join(project.root, filename);
-
-      fs.writeFileSync(filePath, originalContent, { encoding: 'utf8' });
-
-      return blueprint.insertIntoFile(filename, toInsert, {before: line2 + EOL})
-        .then(function(result) {
-          var contents = fs.readFileSync(path.join(project.root, filename), { encoding: 'utf8' });
-
-          expect(contents).to.equal([line1, toInsert, line2, line2, line3].join(EOL),
-                       'inserted contents should be inserted after the `after` value');
-          expect(result.originalContents).to.equal(originalContent, 'returned object should contain original contents');
-          expect(result.inserted).to.equal(true, 'inserted should indicate that the file was modified');
-        });
-    });
-
-
-    it('it will make no change if options.after is not found in the original', function() {
-      var toInsert = 'blahzorz blammo';
-      var originalContent = 'the original content';
-      var filePath = path.join(project.root, filename);
-
-      fs.writeFileSync(filePath, originalContent, { encoding: 'utf8' });
-
-      return blueprint.insertIntoFile(filename, toInsert, {after: 'not found' + EOL})
-        .then(function(result) {
-          var contents = fs.readFileSync(path.join(project.root, filename), { encoding: 'utf8' });
-
-          expect(contents).to.equal(originalContent, 'original content is unchanged');
-          expect(result.originalContents).to.equal(originalContent, 'returned object should contain original contents');
-          expect(result.inserted).to.equal(false, 'inserted should indicate that the file was not modified');
-        });
-    });
-
-    it('it will make no change if options.before is not found in the original', function() {
-      var toInsert = 'blahzorz blammo';
-      var originalContent = 'the original content';
-      var filePath = path.join(project.root, filename);
-
-      fs.writeFileSync(filePath, originalContent, { encoding: 'utf8' });
-
-      return blueprint.insertIntoFile(filename, toInsert, {before: 'not found' + EOL})
-        .then(function(result) {
-          var contents = fs.readFileSync(path.join(project.root, filename), { encoding: 'utf8' });
-
-          expect(contents).to.equal(originalContent, 'original content is unchanged');
-          expect(result.originalContents).to.equal(originalContent, 'returned object should contain original contents');
-          expect(result.inserted).to.equal(false, 'inserted should indicate that the file was not modified');
-        });
-    });
-  });
-
   describe('lookupBlueprint', function() {
     var blueprint;
     var ui;
diff --git a/tests/unit/utilities/insert-into-file-test.js b/tests/unit/utilities/insert-into-file-test.js
new file mode 100644
index 000000000..0f28b4a20
--- /dev/null
+++ b/tests/unit/utilities/insert-into-file-test.js
@@ -0,0 +1,193 @@
+'use strict';
+
+var fs = require('fs-extra');
+var path = require('path');
+var temp = require('temp');
+var EOL = require('os').EOL;
+var insertIntoFile = require('../../../lib/utilities/insert-into-file');
+
+var expect = require('chai').expect;
+
+describe('insertIntoFile()', function() {
+  var tempDir, filePath;
+
+  beforeEach(function() {
+    tempDir = temp.mkdirSync('insert-into-file-test');
+    filePath = path.join(tempDir, 'foo-bar-baz.txt');
+  });
+
+  afterEach(function() {
+    fs.removeSync(tempDir);
+  });
+
+  it('will create the file if not already existing', function() {
+    var toInsert = 'blahzorz blammo';
+
+    return insertIntoFile(filePath, toInsert)
+      .then(function(result) {
+        var contents = fs.readFileSync(filePath, { encoding: 'utf8' });
+
+        expect(contents).to.contain(toInsert);
+        expect(result.originalContents).to.equal('', 'returned object should contain original contents');
+        expect(result.inserted).to.equal(true, 'inserted should indicate that the file was modified');
+        expect(contents).to.equal(result.contents, 'returned object should contain contents');
+      });
+  });
+
+  it('will insert into the file if it already exists', function() {
+    var toInsert = 'blahzorz blammo';
+    var originalContent = 'some original content\n';
+
+    fs.writeFileSync(filePath, originalContent, { encoding: 'utf8' });
+
+    return insertIntoFile(filePath, toInsert)
+      .then(function(result) {
+        var contents = fs.readFileSync(filePath, { encoding: 'utf8' });
+
+        expect(contents).to.equal(originalContent + toInsert, 'inserted contents should be appended to original');
+        expect(result.originalContents).to.equal(originalContent, 'returned object should contain original contents');
+        expect(result.inserted).to.equal(true, 'inserted should indicate that the file was modified');
+      });
+  });
+
+  it('will not insert into the file if it already contains the content', function() {
+    var toInsert = 'blahzorz blammo';
+
+    fs.writeFileSync(filePath, toInsert, { encoding: 'utf8' });
+
+    return insertIntoFile(filePath, toInsert)
+      .then(function(result) {
+        var contents = fs.readFileSync(filePath, { encoding: 'utf8' });
+
+        expect(contents).to.equal(toInsert, 'contents should be unchanged');
+        expect(result.inserted).to.equal(false, 'inserted should indicate that the file was not modified');
+      });
+  });
+
+  it('will insert into the file if it already contains the content if force option is passed', function() {
+    var toInsert = 'blahzorz blammo';
+
+    fs.writeFileSync(filePath, toInsert, { encoding: 'utf8' });
+
+    return insertIntoFile(filePath, toInsert, { force: true })
+      .then(function(result) {
+        var contents = fs.readFileSync(filePath, { encoding: 'utf8' });
+
+        expect(contents).to.equal(toInsert + toInsert, 'contents should be unchanged');
+        expect(result.inserted).to.equal(true, 'inserted should indicate that the file was not modified');
+      });
+  });
+
+  it('will insert into the file after a specified string if options.after is specified', function() {
+    var toInsert = 'blahzorz blammo';
+    var line1 = 'line1 is here';
+    var line2 = 'line2 here';
+    var line3 = 'line3';
+    var originalContent = [line1, line2, line3].join(EOL);
+
+    fs.writeFileSync(filePath, originalContent, { encoding: 'utf8' });
+
+    return insertIntoFile(filePath, toInsert, {after: line2 + EOL})
+      .then(function(result) {
+        var contents = fs.readFileSync(filePath, { encoding: 'utf8' });
+
+        expect(contents).to.equal([line1, line2, toInsert, line3].join(EOL),
+                     'inserted contents should be inserted after the `after` value');
+        expect(result.originalContents).to.equal(originalContent, 'returned object should contain original contents');
+        expect(result.inserted).to.equal(true, 'inserted should indicate that the file was modified');
+      });
+  });
+
+  it('will insert into the file after the first instance of options.after only', function() {
+    var toInsert = 'blahzorz blammo';
+    var line1 = 'line1 is here';
+    var line2 = 'line2 here';
+    var line3 = 'line3';
+    var originalContent = [line1, line2, line2, line3].join(EOL);
+
+    fs.writeFileSync(filePath, originalContent, { encoding: 'utf8' });
+
+    return insertIntoFile(filePath, toInsert, {after: line2 + EOL})
+      .then(function(result) {
+        var contents = fs.readFileSync(filePath, { encoding: 'utf8' });
+
+        expect(contents).to.equal([line1, line2, toInsert, line2, line3].join(EOL),
+                     'inserted contents should be inserted after the `after` value');
+        expect(result.originalContents).to.equal(originalContent, 'returned object should contain original contents');
+        expect(result.inserted).to.equal(true, 'inserted should indicate that the file was modified');
+      });
+  });
+
+  it('will insert into the file before a specified string if options.before is specified', function() {
+    var toInsert = 'blahzorz blammo';
+    var line1 = 'line1 is here';
+    var line2 = 'line2 here';
+    var line3 = 'line3';
+    var originalContent = [line1, line2, line3].join(EOL);
+
+    fs.writeFileSync(filePath, originalContent, { encoding: 'utf8' });
+
+    return insertIntoFile(filePath, toInsert, {before: line2 + EOL})
+      .then(function(result) {
+        var contents = fs.readFileSync(filePath, { encoding: 'utf8' });
+
+        expect(contents).to.equal([line1, toInsert, line2, line3].join(EOL),
+                     'inserted contents should be inserted before the `before` value');
+        expect(result.originalContents).to.equal(originalContent, 'returned object should contain original contents');
+        expect(result.inserted).to.equal(true, 'inserted should indicate that the file was modified');
+      });
+  });
+
+  it('will insert into the file before the first instance of options.before only', function() {
+    var toInsert = 'blahzorz blammo';
+    var line1 = 'line1 is here';
+    var line2 = 'line2 here';
+    var line3 = 'line3';
+    var originalContent = [line1, line2, line2, line3].join(EOL);
+
+    fs.writeFileSync(filePath, originalContent, { encoding: 'utf8' });
+
+    return insertIntoFile(filePath, toInsert, {before: line2 + EOL})
+      .then(function(result) {
+        var contents = fs.readFileSync(filePath, { encoding: 'utf8' });
+
+        expect(contents).to.equal([line1, toInsert, line2, line2, line3].join(EOL),
+                     'inserted contents should be inserted after the `after` value');
+        expect(result.originalContents).to.equal(originalContent, 'returned object should contain original contents');
+        expect(result.inserted).to.equal(true, 'inserted should indicate that the file was modified');
+      });
+  });
+
+
+  it('it will make no change if options.after is not found in the original', function() {
+    var toInsert = 'blahzorz blammo';
+    var originalContent = 'the original content';
+
+    fs.writeFileSync(filePath, originalContent, { encoding: 'utf8' });
+
+    return insertIntoFile(filePath, toInsert, {after: 'not found' + EOL})
+      .then(function(result) {
+        var contents = fs.readFileSync(filePath, { encoding: 'utf8' });
+
+        expect(contents).to.equal(originalContent, 'original content is unchanged');
+        expect(result.originalContents).to.equal(originalContent, 'returned object should contain original contents');
+        expect(result.inserted).to.equal(false, 'inserted should indicate that the file was not modified');
+      });
+  });
+
+  it('it will make no change if options.before is not found in the original', function() {
+    var toInsert = 'blahzorz blammo';
+    var originalContent = 'the original content';
+
+    fs.writeFileSync(filePath, originalContent, { encoding: 'utf8' });
+
+    return insertIntoFile(filePath, toInsert, {before: 'not found' + EOL})
+      .then(function(result) {
+        var contents = fs.readFileSync(filePath, { encoding: 'utf8' });
+
+        expect(contents).to.equal(originalContent, 'original content is unchanged');
+        expect(result.originalContents).to.equal(originalContent, 'returned object should contain original contents');
+        expect(result.inserted).to.equal(false, 'inserted should indicate that the file was not modified');
+      });
+  });
+});