Code coverage report for src/styledocco.js

Statements: 61.54% (56 / 91)      Branches: 47.92% (23 / 48)      Functions: 47.06% (8 / 17)      Lines: 61.8% (55 / 89)      Ignored: none     

All files » src/ » styledocco.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 181 182 183 184 185    1 1 1       1             1                 1   59 8     51 18 33 6 27     27       1   22 66   22     1   11       1       1       1 15 15 15 15   15 8     15 6 14 14 14     15 11   15   15     1                                                   1 15     15 3     15 15       15         15 15       1                                                                             1 15     1 1 1 1  
'use strict';
 
var marked = require('marked');
var util =   require('util');
marked.setOptions({ sanitize: false, gfm: true });
 
// Regular expressions to match comments. We only match comments in
// the beginning of lines. 
var commentRegexs = {
  single: /^\/\//, // Single line comments for Sass, Less and Stylus
  multiStart: /^\/\*/,
  multiEnd: /\*\//
};
 
// Make an URL slug from `str`.
var slugify = function(str) {
  return encodeURIComponent(
    str.trim().toLowerCase()
      .replace(/[^\w ]+/g,'')
      .replace(/ +/g,'-')
  );
};
 
// Check if a string is code or a comment (and which type of comment).
var checkType = function(str) {
  // Treat multi start and end on same row as a single line comment.
  if (str.match(commentRegexs.multiStart) && str.match(commentRegexs.multiEnd)) {
    return 'single';
  // Checking for multi line comments first to avoid matching single line
  // comment symbols inside multi line blocks.
  } else if (str.match(commentRegexs.multiStart)) {
    return 'multistart';
  } else if (str.match(commentRegexs.multiEnd)) {
    return 'multiend';
  } else Iif ((commentRegexs.single != null) && str.match(commentRegexs.single)) {
    return 'single';
  } else {
    return 'code';
  }
};
 
var formatDocs = function(str) {
  // Filter out comment symbols
  for (var key in commentRegexs) {
    str = str.replace(commentRegexs[key], '');
  }
  return str + '\n';
};
 
var formatCode = function(str) {
  // Truncate base64 encoded strings
  return str.replace(/(;base64,)[^\)]*/, '$1...') + '\n';
};
 
// Trim newlines from beginning and end of a multi line string.
var trimNewLines = function(str) {
  return str.replace(/^\n*/, '').replace(/\n*$/, '');
};
 
var htmlEntities = function(str) {
  return str.replace(/</g, '&lt;').replace(/>/g, '&gt;');
};
 
var separate = function(css) {
  var lines = css.split('\n');
  var docs, code, line, blocks = [];
  while (lines.length) {
    docs = code = '';
    // First check for any single line comments.
    while (lines.length && checkType(lines[0]) === 'single') {
      docs += formatDocs(lines.shift());
    }
    // A multi line comment starts here, add lines until comment ends.
    if (lines.length && checkType(lines[0]) === 'multistart') {
      while (lines.length) {
        line = lines.shift();
        docs += formatDocs(line);
        if (checkType(line) === 'multiend') break;
      }
    }
    while (lines.length && (checkType(lines[0]) === 'code' || checkType(lines[0]) === 'multiend')) {
      code += formatCode(lines.shift());
    }
    blocks.push({ docs: docs, code: code });
  }
  return blocks;
};
 
var escapeHtml = function(blocks) {
    return blocks.map(function(block) {
        block.docs.reduce(function(tokens, token) {
          if (token.type === 'code' && (token.lang == null || token.lang === 'html')) {
            token.type = 'html';
            token.pre = true;
            token.text = '<textarea class="preview-code" spellcheck="false">' + htmlEntities(token.text) + '</textarea>';
          // Add permalink `id`s and some custom properties to headings.
            // TODO Move this to the template.
          } else if (token.type === 'heading') {
            var slug = slugify(token.text);
            token.type = 'html';
            token._slug = slug;
            token._origText = token.text;
            // This token should start a new doc section
            if (token.depth === 1) token._split = true;
            token.text = '<h' + token.depth + ' id="' + slug + '">' +
                         token.text + '</h' + token.depth + '>\n';
          }
          tokens.push(token);
          return tokens;
        }, [])
        return block;
    });
}
 
var parseDocs = function(blocks) {
  return blocks
    .map(function(block) {
      // An empty comment containing whitespace is still an empty comment.
      if (/^\W*$/.test(block.docs)) {
        block.docs = '';
      }
      // Run comments through marked.lexer to get Markdown tokens.
      block.docs = marked.lexer(block.docs);
      return block;
    })
    .map(function(block) {
      // If we encounter code blocks in documentation, add preview HTML.
      var newBlock = {
        code: block.code,
        docs: block.docs
      };
      // Keep marked's custom links property on the docs array.
      newBlock.docs.links = block.docs.links;
      return newBlock;
    }, [])
}
 
var makeSections = exports.makeSections = function(blocks) {
  return blocks
    .reduce(function(sections, cur) {
      // Split into sections with headings as delimiters.
      var docs = cur.docs;
      while (util.isArray(docs) && docs.length) {
        // New or first section, add title/slug properties.
        if (docs[0]._split || sections.length === 0) {
          var title = docs[0]._origText;
          var slug = docs[0]._slug;
          sections.push({ docs: [ docs.shift() ], code: '',
                          title: title, slug: slug });
        } else {
          // Add the documentation to the last section.
          sections[sections.length-1].docs.push(docs.shift());
        }
        // Keep marked's custom links property on the docs arrays.
        sections[sections.length-1].docs.links = docs.links || {};
      }
      // No docs in file, just add the CSS.
      if (sections.length === 0) {
        sections.push(cur);
      // Add remaining code to the last section.
      } else {
        sections[sections.length-1].code += cur.code;
      }
      return sections;
    }, [])
    .map(function(section) {
      // Run through marked parser to generate HTML.
      return {
        title: section.title ? section.title.trim() : '',
        slug: section.slug || '',
        docs: trimNewLines(marked.parser(section.docs)),
        code: trimNewLines(section.code)
      };
    });
};
 
module.exports = function(css) {
  return parseDocs(separate(css));
};
 
module.exports.makeSections = makeSections;
module.exports.separate = separate;
module.exports.parseDocs = parseDocs;
module.exports.escapeHtml = escapeHtml;