Code coverage report for lib/markup.js

Statements: 71.56% (78 / 109)      Branches: 70.31% (45 / 64)      Functions: 82.35% (14 / 17)      Lines: 73.12% (68 / 93)      Ignored: none     

All files » lib/ » markup.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    1 1   1 197 197 197 197     1 1 1 41 31 31 31                                                         31 31 31   1 47           1 33     33     1 222 95 95   160   1 41 41   41 39       1 127 127   127 127 127 33 33 22   33 22   127   1 191 191         191 191 171 150 117 70 70 70   47       80     80 80     191   1 47 27     1       1 31   1   1  
// Deals with blessed-style {bold}tags{/bold}
 
var logger = require('./logger');
var blessed = require('blessed');
 
function Markup (style) {
  var self = this;
  self.style = style || '';
  self.contents = [];
  [].slice.call(arguments, 1).forEach(function (arg) { self.push(arg); });
}
 
Markup.TAG_RE = /\{(\/?)([\w\-,;!#]*)\}/;
Markup.TAG_RE_G = new RegExp(Markup.TAG_RE.source, 'g');
Markup.parse = function (text) {
  if (text instanceof Markup) return text;
  var markup = new Markup();
  var hierarchy = [markup], match;
  while (match = text.match(Markup.TAG_RE)) {
    var tag = match[0];
    var parent = hierarchy[hierarchy.length - 1];
    if (match.index) parent.push(text.slice(0, match.index));
    if (!match[1]) { // open tag
      var replace = {open: '{', close: '}'}[match[2]];
      if (replace) {
        parent.push(replace);
      } else {
        var newMarkup = new Markup(match[0]);
        parent.push(newMarkup);
        hierarchy.push(newMarkup);
      }
    } else { // close tag
      var closed;
      if (match[0] === '{/}') closed = hierarchy.splice(1, Infinity);
      else if (parent.style === '{'+match[2]+'}') closed = [hierarchy.pop()];
      else throw new Error("invalid close tag");
      var lastItem = hierarchy[hierarchy.length - 1];
      closed.some(function (item) {
        if (!item.contents.length) {
          lastItem.contents.pop();
          return true;
        }
        lastItem = item;
      });
    }
    text = text.slice(match.index + tag.length);
  }
  Iif (hierarchy.length !== 1) throw new Error("mismatched tag");
  if (text) markup.push(text);
  return markup.clean();
};
Markup.closeTags = function (markedUp) {
  return (markedUp
    .replace(Markup.TAG_RE_G, '{/$2}', 'g') // 'g' flag ignored :(
    .match(Markup.TAG_RE_G) || [])
    .reverse()
    .join('');
};
Markup.getTaglessLength = function (val) {
  Iif (val instanceof Markup) return val.contents.reduce(function (total, item) {
    return total + Markup.getTaglessLength(item);
  }, 0);
  return val.length;
};
 
Markup.prototype.clean = function () {
  if (!this.style && this.contents.length === 1) {
    var child = this.contents[0];
    if (child instanceof Markup) return child;
  }
  return this;
};
Markup.prototype.tag = function (style, start, end) {
  if (typeof start !== 'number') start = 0;
  if (typeof end !== 'number') end = Infinity;
 
  if (!style) return this;
  return this.slice(0, start).push(
    new Markup(style, this.slice(start, end)),
    this.slice(end));
};
Markup.prototype.slice = function (start, end) {
  Iif (typeof start !== 'number') start = 0;
  if (typeof end !== 'number') end = Infinity;
 
  var i = 0;
  var markup = new Markup(this.style);
  this.contents.some(function (item) {
    var nextI = i + Markup.getTaglessLength(item);
    if (start < nextI && end >= i) {
      markup.push(item.slice(Math.max(0, start - i), Math.max(0, end - i)));
    }
    if (nextI >= end) return true;
    i = nextI;
  });
  return markup;
};
Markup.prototype.push = function () {
  var self = this;
  var contents = self.contents;
 
  // unoptimized version of the following:
  // contents.push.apply(contents, arguments);
 
  var lastItem = contents[contents.length - 1];
  [].forEach.call(arguments, function (item) {
    if (!item) return;
    if (item instanceof Markup) {
      if (!item.style || item.style === self.style) {
        self.push.apply(self, item.contents);
        lastItem = contents[contents.length - 1];
        return;
      }
      Iif (lastItem instanceof Markup && item.style === lastItem.style) {
        return lastItem.push.apply(lastItem, item.contents);
      }
    }
    Iif (typeof item === 'string' && typeof lastItem === 'string') {
      return contents[contents.length - 1] += item;
    }
    contents.push(item);
    lastItem = item;
  });
 
  return self.clean();
};
Markup.prototype.toString = function () {
  return this.style + this.contents.map(function (item) {
    return typeof item === 'string' ? blessed.escape(item) : item;
  }).join('') + Markup.closeTags(this.style);
};
Object.defineProperty(Markup.prototype, 'length', {get: function () {
  return this.toString().length;
}});
 
function markup (text, style, start, end) {
  return Markup.parse(text).tag(style, start, end);
}
markup.parse = Markup.parse;
 
module.exports = markup;