Source: lib/templating.js

'use strict';
var path = require('path');
var fs = require('fs');

/**
 * @namespace yo-utils.templating
 * @borrows module:yo-utils/lib/templating.relativePathTo as relativePathTo
 * @borrows module:yo-utils/lib/templating.rewriteFile as rewriteFile
 * @borrows module:yo-utils/lib/templating.rewrite as rewrite
 * @borrows module:yo-utils/lib/templating.processDirectory as processDirectory
 */

/**
 * Templating related utilities
 * @module yo-utils/lib/templating
 */

/**
 * Returns a relative path used to require the 'to' file in the 'from' file
 * @alias module:yo-utils/lib/templating.relativePathTo
 * @param  {String}  from  - file path to the from file
 * @param  {String}  to    - file path to the to file
 * @param  {Boolean} strip - whether to strip the index file name and/or the js ext
 * @return {String}        - relative path to be used in require statements
 */
function relativePathTo(from, to, strip) {
  var relPath = path.relative(from.replace(path.basename(from), ''), to);
  if (relPath.match(/^\.\.?(\/|\\)/) === null) { relPath = './' + relPath; }
  if (strip) { return relPath.replace(/((\/|\\)index\.js|\.js)$/, ''); }
  return relPath;
}

/**
 * Rewrite a single file in place
 * @alias module:yo-utils/lib/templating.rewriteFile
 * @param  {Object} args - rewrite arguments, file, and path
 */
function rewriteFile(args) {
  args.path = args.path || process.cwd();
  var fullPath = path.join(args.path, args.file);

  args.haystack = fs.readFileSync(fullPath, 'utf8');
  var body = rewrite(args);

  fs.writeFileSync(fullPath, body);
}

/**
 * Rewrite a body of text
 * @alias module:yo-utils/lib/templating.rewrite
 * @param  {Object} args - rewrite arguments
 * @return {String}      - the rewritten body of text
 */
function rewrite(args) {
  // check if splicable is already in the body text
  var re = new RegExp(args.splicable.map(function(line) {
    return '\\s*' + escapeRegExp(line);
  }).join('\n'));

  if (re.test(args.haystack)) {
    return args.haystack;
  }

  var lines = args.haystack.split('\n');

  var otherwiseLineIndex = -1;
  lines.forEach(function(line, i) {
    if (line.indexOf(args.needle) !== -1) {
      otherwiseLineIndex = i;
    }
  });
  if (otherwiseLineIndex === -1) { return lines.join('\n'); }

  var spaces = 0;
  while (lines[otherwiseLineIndex].charAt(spaces) === ' ') {
    spaces += 1;
  }

  var spaceStr = '';
  while (spaces > 0) {
    spaces -= 1;
    spaceStr += ' ';
  }

  lines.splice(otherwiseLineIndex + 1, 0, args.splicable.map(function(line) {
    return spaceStr + line;
  }).join('\n'));

  return lines.join('\n');
}

/**
 * Process an entire directory filtering and templating accordingly
 * @alias module:yo-utils/lib/templating.processDirectory
 * @param  {Object} self        - the generator
 * @param  {String} source      - the path to the directory to be processed
 * @param  {String} destination - the path to where the processed direcory should be written to
 */
function processDirectory(self, source, destination) {
  var root = self.isPathAbsolute(source) ? source : path.join(self.sourceRoot(), source);
  var files = self.expandFiles('**', { dot: true, cwd: root });
  var dest;
  var src;

  files.forEach(function(f) {
    var filteredFile = filterFile(f);
    if (self.name) {
      filteredFile.name = filteredFile.name.replace('name', self.name);
    }
    var name = filteredFile.name;
    var copy = false;
    var stripped;

    src = path.join(root, f);
    dest = path.join(destination, name);

    if (path.basename(dest).indexOf('_') === 0) {
      stripped = path.basename(dest).replace(/^_/, '');
      dest = path.join(path.dirname(dest), stripped);
    }

    if (path.basename(dest).indexOf('!') === 0) {
      stripped = path.basename(dest).replace(/^!/, '');
      dest = path.join(path.dirname(dest), stripped);
      copy = true;
    }

    if (templateIsUsable(self, filteredFile)) {
      if (copy) {
        self.copy(src, dest);
      } else {
        self.template(src, dest);
      }
    }
  });
}

module.exports.relativePathTo = relativePathTo;
module.exports.rewriteFile = rewriteFile;
module.exports.rewrite = rewrite;
module.exports.processDirectory = processDirectory;

/**
 * Private functions
 */

/**
 * Escape regexp special chars
 * @param  {String} str - the string to be escaped
 * @return {String}     - the escaped string
 */
function escapeRegExp(str) {
  return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
}

/**
 * Parse a filtered filename and return the processed name and filters
 * @param  {String} template - the file path to the template
 * @return {Object}          - contains the processed name and filters array
 */
function filterFile(template) {
  // Find matches for parans
  var filterMatches = template.match(/\(([^)]+)\)/g);
  var filters = [];
  if (filterMatches) {
    filterMatches.forEach(function(filter) {
      filters.push(filter.replace('(', '').replace(')', ''));
      template = template.replace(filter, '');
    });
  }

  return { name: template, filters: filters };
}

/**
 * Check whether or not a template is usable based on filters
 * @param  {Object} self         - the generator
 * @param  {Object} filteredFile - the processed template object
 * @return {Boolean}             - whether the template is usable based on filters
 */
function templateIsUsable(self, filteredFile) {
  var filters = self.config.get('filters');
  var enabledFilters = [];
  for (var key in filters) {
    if (filters[key]) { enabledFilters.push(key); }
  }
  var matchedFilters = self._.intersection(filteredFile.filters, enabledFilters);
  // check that all filters on file are matched
  if (filteredFile.filters.length && matchedFilters.length !== filteredFile.filters.length) {
    return false;
  }
  return true;
}