saneef/metalsmith-hyphenate

View on GitHub
index.js

Summary

Maintainability
B
4 hrs
Test Coverage
'use strict';

var debug = require('debug')('metalsmith-hyphenate');
var extname = require('path').extname;
var p5 = require('parse5');
var Hypher = require('hypher');
var minimatch = require('minimatch');

/**
 * Default Elements to hyphenate
 */
var DEFAULT_ELEMENTS = ['p', 'li', 'ol'];

/**
 * Data attribute name
 */
var HYPHENATE_DATA_ATTR = 'data-hyphenate';

/**
 * A Metalsmith plugin to add soft hyphens in HTML
 *
 * @param {Object} options (optional)
 *   @property {Array} [elements=['p', 'li', 'ol']] - HTML elements which needs to be hyphenated
 *   @property {Array} [langModule='hyphenation.en-us'] - Hypher language module(pattern) to use
 *   @property {String|Array} [ignore] - Glob expressions to ignore a file, or a set of files
 * @return {Function}
 */

function plugin(options) {
  options = options || {};

  options.elements = options.elements || DEFAULT_ELEMENTS;
  options.langModule = options.langModule || 'hyphenation.en-us';

  if ((options.ignore !== undefined) &&
    (Object.prototype.toString.call(options.ignore) === '[object String]')) {
    options.ignore = [options.ignore];
  }
  debug('File ignore glob expressions: %j', options.ignore);

  try {
    var hypher = new Hypher(require(options.langModule));
  } catch (err) {
    console.log("Language module %s is not installed. Try 'npm install %s'",
      options.langModule, options.langModule);
  }

  /**
   * Check if a `value` is present in the array.
   *
   * @param {array} arr
   * @param value
   * @return {Boolean}
   */
  function isPresent(arr, value) {
    return arr.indexOf(value) < 0 ? false : true;
  }

  /**
   * Check if a `file` is HTML.
   *
   * @param {String} file
   * @return {Boolean}
   */

  function isHtml(file) {
    return /\.html?/.test(extname(file));
  }

  /**
   * Traverses throught parsed DOM, and hyphenate text nodes
   *
   * @param {String} domString
   * @return {String}
   */
  function hyphenateText(dom, forceHyphenateAllChildren) {
    if (dom.childNodes !== undefined) {
      dom.childNodes.forEach(function(node) {
        if (node.nodeName === '#text' && !isSkippable(node) && (forceHyphenateAllChildren || isPresent(options.elements, dom.tagName))) {
          node.value = hypher.hyphenateText(node.value);
        } else {
          hyphenateText(node, isPresent(options.elements, dom.tagName));
        }
      });
    }

    return dom;
  }

  /**
   * Check if the node is marked for skip
   *
   * @param {String} DOM node
   * @return {Boolean}
   */
   function isSkippable(node) {
    var result = false;
    if (node && node.parentNode && node.parentNode.attrs && Array.isArray(node.parentNode.attrs)) {
      node.parentNode.attrs.forEach(function(attr) {
        if (attr.name && attr.value && attr.name === HYPHENATE_DATA_ATTR && attr.value === 'false') {
          result = true;
        }
      });
    }

    return result;
   }

  /**
   * Check if the file matches the ignore glob patterns
   *
   * @param {String} file
   * @return {Boolean}
   */
  function isIgnoredFile(file) {
    if (options.ignore !== undefined) {
      var result = false;

      options.ignore.forEach(function(pattern) {
        if (minimatch(file, pattern)) {
          result = true;
          return false;
        }
      });

      return result;
    }

    return false;
  }

  return function(files, metalsmith, done) {
    setImmediate(done);

    Object.keys(files).forEach(function(file) {
      debug('checking if file matches ignore patterns: %s', file);
      if (isIgnoredFile(file) === true) {
        return;
      }

      debug('checking if it is an HTML file: %s', file);
      if (isHtml(file) === false) {
        return;
      }

      debug('hyphenating "%s"', file);
      var dom = p5.parse(files[file].contents.toString());
      dom = hyphenateText(dom);
      files[file].contents = new Buffer(p5.serialize(dom));
    });
  };
}

/**
 * Expose `plugin`.
 */
module.exports = plugin;