Skip to content
This repository
Browse code

Make highlighter manually bundled and bundle remark-specific resources

on every build. Also, update highlighting style when config is updated.
  • Loading branch information...
commit 46971a511de04e858910a0fd826eb8d448babd31 1 parent fec4231
Ole Petter Bang authored March 06, 2013
1  .jshintignore
... ...
@@ -1 +1,2 @@
  1
+src/remark/highlighter.js
1 2
 src/remark/resources.js
32  make.js
@@ -10,9 +10,9 @@ target.all = function () {
10 10
   target.minify();
11 11
 };
12 12
 
13  
-target.resources = function () {
14  
-  console.log('Compiling resources...');
15  
-  compileResources('src/remark/resources.js');
  13
+target.highlighter = function () {
  14
+  console.log('Bundling highlighter...');
  15
+  bundleHighlighter('src/remark/highlighter.js');
16 16
 };
17 17
 
18 18
 target.lint = function () {
@@ -27,6 +27,7 @@ target.test = function () {
27 27
 
28 28
 target.bundle = function () {
29 29
   console.log('Bundling...');
  30
+  bundleResources('src/remark/resources.js');
30 31
   run('browserify src/remark.js', {silent: true}).output.to('remark.js');
31 32
 };
32 33
 
@@ -42,14 +43,25 @@ var path = require('path')
42 43
   , ignoredStyles = ['brown_paper', 'school_book', 'pojoaque']
43 44
   ;
44 45
 
45  
-function compileResources (target) {
46  
-  var highlightjs = 'vendor/highlight.js/src/'
47  
-    , resources = {
  46
+function bundleResources (target) {
  47
+  var resources = {
48 48
         DOCUMENT_STYLES: JSON.stringify(
49 49
           less('src/remark.less'))
50  
-      , OVERLAY_TEMPLATE: JSON.stringify(
51  
-          cat('src/overlay.html.template'))
52  
-      , HIGHLIGHTER_STYLES: JSON.stringify(
  50
+      , OVERLAY: JSON.stringify(
  51
+          cat('src/overlay.html'))
  52
+      };
  53
+
  54
+  cat('src/resources.js.template')
  55
+    .replace(/%(\w+)%/g, function (match, key) {
  56
+      return resources[key];
  57
+    })
  58
+    .to(target);
  59
+}
  60
+
  61
+function bundleHighlighter (target) {
  62
+  var highlightjs = 'vendor/highlight.js/src/'
  63
+    , resources = {
  64
+        HIGHLIGHTER_STYLES: JSON.stringify(
53 65
           ls(highlightjs + 'styles/*.css').reduce(mapStyle, {}))
54 66
       , HIGHLIGHTER_ENGINE: 
55 67
           cat(highlightjs + 'highlight.js')
@@ -60,7 +72,7 @@ function compileResources (target) {
60 72
           }).join(',')
61 73
       };
62 74
 
63  
-  cat('src/resources.js.template')
  75
+  cat('src/highlighter.js.template')
64 76
     .replace(/%(\w+)%/g, function (match, key) {
65 77
       return resources[key];
66 78
     })
3,490  remark.js
1743 additions, 1747 deletions not shown
8  remark.min.js
4 additions, 4 deletions not shown
14  src/highlighter.js.template
... ...
@@ -0,0 +1,14 @@
  1
+/* Automatically generated */
  2
+
  3
+var hljs = new (%HIGHLIGHTER_ENGINE%)()
  4
+  , languages = [%HIGHLIGHTER_LANGUAGES%]
  5
+  ;
  6
+
  7
+for (var i = 0; i < languages.length; ++i) {
  8
+  hljs.LANGUAGES[languages[i].name] = languages[i].create(hljs);
  9
+}
  10
+
  11
+module.exports = {
  12
+  styles: %HIGHLIGHTER_STYLES%,
  13
+  engine: hljs
  14
+};
0  src/overlay.html.template → src/overlay.html
File renamed without changes
17  src/remark.js
... ...
@@ -1,5 +1,7 @@
1 1
 var utils = require('./remark/utils')
2 2
   , api = require('./remark/api')
  3
+  , config = require('./remark/config')
  4
+  , events = require('./remark/events')
3 5
   , Controller = require('./remark/controller').Controller
4 6
   , Dispatcher = require('./remark/dispatcher')
5 7
   , highlighter = require('./remark/highlighter')
@@ -44,13 +46,22 @@ function assureElementsExist (sourceElement, slideshowElement) {
44 46
 function styleDocument () {
45 47
   var styleElement = document.createElement('style')
46 48
     , headElement = document.getElementsByTagName('head')[0]
47  
-    , styles = resources.documentStyles + highlighter.cssForStyle()
48 49
     ;
49 50
 
50 51
   styleElement.type = 'text/css';
51  
-  styleElement.innerHTML = styles;
52  
-
53 52
   headElement.insertBefore(styleElement, headElement.firstChild);
  53
+
  54
+  events.on('config', onConfig);
  55
+  onConfig();
  56
+
  57
+  function onConfig () {
  58
+    if (config.highlightStyle === undefined) {
  59
+      config.highlightStyle = 'default';
  60
+    }
  61
+    
  62
+    styleElement.innerHTML = resources.documentStyles +
  63
+      highlighter.styles[config.highlightStyle] || '';
  64
+  }
54 65
 }
55 66
 
56 67
 function setupSlideshow (sourceElement, slideshowElement) {
9  src/remark/api.js
... ...
@@ -1,8 +1,15 @@
1 1
 var EventEmitter = require('events').EventEmitter
2  
-  , api = module.exports = new EventEmitter()
  2
+  , highlighter = require('./highlighter')
3 3
   , events = require('./events')
  4
+  , api = module.exports = new EventEmitter()
4 5
   ;
5 6
 
  7
+api.highlighter = {
  8
+  engine: function() {
  9
+    return highlighter.engine;
  10
+  }
  11
+};
  12
+
6 13
 api.loadFromString = function (source) {
7 14
   events.emit('loadFromString', source);
8 15
 };
2,176  src/remark/highlighter.js
... ...
@@ -1,37 +1,2165 @@
1  
-var api = require('./api')
2  
-  , config = require('./config')
3  
-  , resources = require('./resources')
  1
+/* Automatically generated */
4 2
 
5  
-  , highlighter = module.exports = {}
6  
-  ;
  3
+var hljs = new (/*
  4
+Syntax highlighting with language autodetection.
  5
+http://softwaremaniacs.org/soft/highlight/
  6
+*/
  7
+
  8
+function() {
  9
+
  10
+  /* Utility functions */
7 11
 
8  
-api.highlighter = {
9  
-  engine: function() {
10  
-    return resources.highlighter.engine;
  12
+  function escape(value) {
  13
+    return value.replace(/&/gm, '&amp;').replace(/</gm, '&lt;');
11 14
   }
12  
-};
13 15
 
14  
-highlighter.cssForStyle = function () {
15  
-  if (config.highlightStyle === undefined) {
16  
-    config.highlightStyle = 'default';
  16
+  function findCode(pre) {
  17
+    for (var node = pre.firstChild; node; node = node.nextSibling) {
  18
+      if (node.nodeName == 'CODE')
  19
+        return node;
  20
+      if (!(node.nodeType == 3 && node.nodeValue.match(/\s+/)))
  21
+        break;
  22
+    }
17 23
   }
18 24
 
19  
-  if (config.highlightStyle === null) {
20  
-    return '';
  25
+  function blockText(block, ignoreNewLines) {
  26
+    return Array.prototype.map.call(block.childNodes, function(node) {
  27
+      if (node.nodeType == 3) {
  28
+        return ignoreNewLines ? node.nodeValue.replace(/\n/g, '') : node.nodeValue;
  29
+      }
  30
+      if (node.nodeName == 'BR') {
  31
+        return '\n';
  32
+      }
  33
+      return blockText(node, ignoreNewLines);
  34
+    }).join('');
21 35
   }
22 36
 
23  
-  return resources.highlighter.styles[config.highlightStyle];
24  
-};
  37
+  function blockLanguage(block) {
  38
+    var classes = (block.className + ' ' + block.parentNode.className).split(/\s+/);
  39
+    classes = classes.map(function(c) {return c.replace(/^language-/, '')});
  40
+    for (var i = 0; i < classes.length; i++) {
  41
+      if (languages[classes[i]] || classes[i] == 'no-highlight') {
  42
+        return classes[i];
  43
+      }
  44
+    }
  45
+  }
  46
+
  47
+  /* Stream merging */
  48
+
  49
+  function nodeStream(node) {
  50
+    var result = [];
  51
+    (function _nodeStream(node, offset) {
  52
+      for (var child = node.firstChild; child; child = child.nextSibling) {
  53
+        if (child.nodeType == 3)
  54
+          offset += child.nodeValue.length;
  55
+        else if (child.nodeName == 'BR')
  56
+          offset += 1;
  57
+        else if (child.nodeType == 1) {
  58
+          result.push({
  59
+            event: 'start',
  60
+            offset: offset,
  61
+            node: child
  62
+          });
  63
+          offset = _nodeStream(child, offset);
  64
+          result.push({
  65
+            event: 'stop',
  66
+            offset: offset,
  67
+            node: child
  68
+          });
  69
+        }
  70
+      }
  71
+      return offset;
  72
+    })(node, 0);
  73
+    return result;
  74
+  }
  75
+
  76
+  function mergeStreams(stream1, stream2, value) {
  77
+    var processed = 0;
  78
+    var result = '';
  79
+    var nodeStack = [];
  80
+
  81
+    function selectStream() {
  82
+      if (stream1.length && stream2.length) {
  83
+        if (stream1[0].offset != stream2[0].offset)
  84
+          return (stream1[0].offset < stream2[0].offset) ? stream1 : stream2;
  85
+        else {
  86
+          /*
  87
+          To avoid starting the stream just before it should stop the order is
  88
+          ensured that stream1 always starts first and closes last:
  89
+
  90
+          if (event1 == 'start' && event2 == 'start')
  91
+            return stream1;
  92
+          if (event1 == 'start' && event2 == 'stop')
  93
+            return stream2;
  94
+          if (event1 == 'stop' && event2 == 'start')
  95
+            return stream1;
  96
+          if (event1 == 'stop' && event2 == 'stop')
  97
+            return stream2;
  98
+
  99
+          ... which is collapsed to:
  100
+          */
  101
+          return stream2[0].event == 'start' ? stream1 : stream2;
  102
+        }
  103
+      } else {
  104
+        return stream1.length ? stream1 : stream2;
  105
+      }
  106
+    }
  107
+
  108
+    function open(node) {
  109
+      function attr_str(a) {return ' ' + a.nodeName + '="' + escape(a.value) + '"'};
  110
+      return '<' + node.nodeName + Array.prototype.map.call(node.attributes, attr_str).join('') + '>';
  111
+    }
  112
+
  113
+    while (stream1.length || stream2.length) {
  114
+      var current = selectStream().splice(0, 1)[0];
  115
+      result += escape(value.substr(processed, current.offset - processed));
  116
+      processed = current.offset;
  117
+      if ( current.event == 'start') {
  118
+        result += open(current.node);
  119
+        nodeStack.push(current.node);
  120
+      } else if (current.event == 'stop') {
  121
+        var node, i = nodeStack.length;
  122
+        do {
  123
+          i--;
  124
+          node = nodeStack[i];
  125
+          result += ('</' + node.nodeName.toLowerCase() + '>');
  126
+        } while (node != current.node);
  127
+        nodeStack.splice(i, 1);
  128
+        while (i < nodeStack.length) {
  129
+          result += open(nodeStack[i]);
  130
+          i++;
  131
+        }
  132
+      }
  133
+    }
  134
+    return result + escape(value.substr(processed));
  135
+  }
  136
+
  137
+  /* Initialization */
  138
+
  139
+  function compileLanguage(language) {
  140
+
  141
+    function langRe(value, global) {
  142
+      return RegExp(
  143
+        value,
  144
+        'm' + (language.case_insensitive ? 'i' : '') + (global ? 'g' : '')
  145
+      );
  146
+    }
  147
+
  148
+    function compileMode(mode, parent) {
  149
+      if (mode.compiled)
  150
+        return;
  151
+      mode.compiled = true;
  152
+
  153
+      var keywords = []; // used later with beginWithKeyword but filled as a side-effect of keywords compilation
  154
+      if (mode.keywords) {
  155
+        var compiled_keywords = {};
  156
+
  157
+        function flatten(className, str) {
  158
+          str.split(' ').forEach(function(kw) {
  159
+            var pair = kw.split('|');
  160
+            compiled_keywords[pair[0]] = [className, pair[1] ? Number(pair[1]) : 1];
  161
+            keywords.push(pair[0]);
  162
+          });
  163
+        }
  164
+
  165
+        mode.lexemsRe = langRe(mode.lexems || hljs.IDENT_RE, true);
  166
+        if (typeof mode.keywords == 'string') { // string
  167
+          flatten('keyword', mode.keywords)
  168
+        } else {
  169
+          for (var className in mode.keywords) {
  170
+            if (!mode.keywords.hasOwnProperty(className))
  171
+              continue;
  172
+            flatten(className, mode.keywords[className]);
  173
+          }
  174
+        }
  175
+        mode.keywords = compiled_keywords;
  176
+      }
  177
+      if (parent) {
  178
+        if (mode.beginWithKeyword) {
  179
+          mode.begin = '\\b(' + keywords.join('|') + ')\\s';
  180
+        }
  181
+        mode.beginRe = langRe(mode.begin ? mode.begin : '\\B|\\b');
  182
+        if (!mode.end && !mode.endsWithParent)
  183
+          mode.end = '\\B|\\b';
  184
+        if (mode.end)
  185
+          mode.endRe = langRe(mode.end);
  186
+        mode.terminator_end = mode.end || '';
  187
+        if (mode.endsWithParent && parent.terminator_end)
  188
+          mode.terminator_end += (mode.end ? '|' : '') + parent.terminator_end;
  189
+      }
  190
+      if (mode.illegal)
  191
+        mode.illegalRe = langRe(mode.illegal);
  192
+      if (mode.relevance === undefined)
  193
+        mode.relevance = 1;
  194
+      if (!mode.contains) {
  195
+        mode.contains = [];
  196
+      }
  197
+      for (var i = 0; i < mode.contains.length; i++) {
  198
+        if (mode.contains[i] == 'self') {
  199
+          mode.contains[i] = mode;
  200
+        }
  201
+        compileMode(mode.contains[i], mode);
  202
+      }
  203
+      if (mode.starts) {
  204
+        compileMode(mode.starts, parent);
  205
+      }
  206
+
  207
+      var terminators = [];
  208
+      for (var i = 0; i < mode.contains.length; i++) {
  209
+        terminators.push(mode.contains[i].begin);
  210
+      }
  211
+      if (mode.terminator_end) {
  212
+        terminators.push(mode.terminator_end);
  213
+      }
  214
+      if (mode.illegal) {
  215
+        terminators.push(mode.illegal);
  216
+      }
  217
+      mode.terminators = terminators.length ? langRe(terminators.join('|'), true) : {exec: function(s) {return null;}};
  218
+    }
  219
+
  220
+    compileMode(language);
  221
+  }
  222
+
  223
+  /*
  224
+  Core highlighting function. Accepts a language name and a string with the
  225
+  code to highlight. Returns an object with the following properties:
  226
+
  227
+  - relevance (int)
  228
+  - keyword_count (int)
  229
+  - value (an HTML string with highlighting markup)
  230
+
  231
+  */
  232
+  function highlight(language_name, value) {
  233
+
  234
+    function subMode(lexem, mode) {
  235
+      for (var i = 0; i < mode.contains.length; i++) {
  236
+        var match = mode.contains[i].beginRe.exec(lexem);
  237
+        if (match && match.index == 0) {
  238
+          return mode.contains[i];
  239
+        }
  240
+      }
  241
+    }
  242
+
  243
+    function endOfMode(mode, lexem) {
  244
+      if (mode.end && mode.endRe.test(lexem)) {
  245
+        return mode;
  246
+      }
  247
+      if (mode.endsWithParent) {
  248
+        return endOfMode(mode.parent, lexem);
  249
+      }
  250
+    }
  251
+
  252
+    function isIllegal(lexem, mode) {
  253
+      return mode.illegal && mode.illegalRe.test(lexem);
  254
+    }
  255
+
  256
+    function keywordMatch(mode, match) {
  257
+      var match_str = language.case_insensitive ? match[0].toLowerCase() : match[0];
  258
+      return mode.keywords.hasOwnProperty(match_str) && mode.keywords[match_str];
  259
+    }
  260
+
  261
+    function processKeywords() {
  262
+      var buffer = escape(mode_buffer);
  263
+      if (!top.keywords)
  264
+        return buffer;
  265
+      var result = '';
  266
+      var last_index = 0;
  267
+      top.lexemsRe.lastIndex = 0;
  268
+      var match = top.lexemsRe.exec(buffer);
  269
+      while (match) {
  270
+        result += buffer.substr(last_index, match.index - last_index);
  271
+        var keyword_match = keywordMatch(top, match);
  272
+        if (keyword_match) {
  273
+          keyword_count += keyword_match[1];
  274
+          result += '<span class="'+ keyword_match[0] +'">' + match[0] + '</span>';
  275
+        } else {
  276
+          result += match[0];
  277
+        }
  278
+        last_index = top.lexemsRe.lastIndex;
  279
+        match = top.lexemsRe.exec(buffer);
  280
+      }
  281
+      return result + buffer.substr(last_index);
  282
+    }
  283
+
  284
+    function processSubLanguage() {
  285
+      if (top.subLanguage && !languages[top.subLanguage]) {
  286
+        return escape(mode_buffer);
  287
+      }
  288
+      var result = top.subLanguage ? highlight(top.subLanguage, mode_buffer) : highlightAuto(mode_buffer);
  289
+      // Counting embedded language score towards the host language may be disabled
  290
+      // with zeroing the containing mode relevance. Usecase in point is Markdown that
  291
+      // allows XML everywhere and makes every XML snippet to have a much larger Markdown
  292
+      // score.
  293
+      if (top.relevance > 0) {
  294
+        keyword_count += result.keyword_count;
  295
+        relevance += result.relevance;
  296
+      }
  297
+      return '<span class="' + result.language  + '">' + result.value + '</span>';
  298
+    }
  299
+
  300
+    function processBuffer() {
  301
+      return top.subLanguage !== undefined ? processSubLanguage() : processKeywords();
  302
+    }
  303
+
  304
+    function startNewMode(mode, lexem) {
  305
+      var markup = mode.className? '<span class="' + mode.className + '">': '';
  306
+      if (mode.returnBegin) {
  307
+        result += markup;
  308
+        mode_buffer = '';
  309
+      } else if (mode.excludeBegin) {
  310
+        result += escape(lexem) + markup;
  311
+        mode_buffer = '';
  312
+      } else {
  313
+        result += markup;
  314
+        mode_buffer = lexem;
  315
+      }
  316
+      top = Object.create(mode, {parent: {value: top}});
  317
+      relevance += mode.relevance;
  318
+    }
  319
+
  320
+    function processModeInfo(buffer, lexem) {
  321
+      mode_buffer += buffer;
  322
+      if (lexem === undefined) {
  323
+        result += processBuffer();
  324
+        return;
  325
+      }
  326
+
  327
+      var new_mode = subMode(lexem, top);
  328
+      if (new_mode) {
  329
+        result += processBuffer();
  330
+        startNewMode(new_mode, lexem);
  331
+        return new_mode.returnBegin;
  332
+      }
  333
+
  334
+      var end_mode = endOfMode(top, lexem);
  335
+      if (end_mode) {
  336
+        if (!(end_mode.returnEnd || end_mode.excludeEnd)) {
  337
+          mode_buffer += lexem;
  338
+        }
  339
+        result += processBuffer();
  340
+        do {
  341
+          if (top.className) {
  342
+            result += '</span>';
  343
+          }
  344
+          top = top.parent;
  345
+        } while (top != end_mode.parent);
  346
+        if (end_mode.excludeEnd) {
  347
+          result += escape(lexem);
  348
+        }
  349
+        mode_buffer = '';
  350
+        if (end_mode.starts) {
  351
+          startNewMode(end_mode.starts, '');
  352
+        }
  353
+        return end_mode.returnEnd;
  354
+      }
  355
+
  356
+      if (isIllegal(lexem, top))
  357
+        throw 'Illegal';
  358
+    }
  359
+
  360
+    var language = languages[language_name];
  361
+    compileLanguage(language);
  362
+    var top = language;
  363
+    var mode_buffer = '';
  364
+    var relevance = 0;
  365
+    var keyword_count = 0;
  366
+    var result = '';
  367
+    try {
  368
+      var match, index = 0;
  369
+      while (true) {
  370
+        top.terminators.lastIndex = index;
  371
+        match = top.terminators.exec(value);
  372
+        if (!match)
  373
+          break;
  374
+        var return_lexem = processModeInfo(value.substr(index, match.index - index), match[0]);
  375
+        index = match.index + (return_lexem ? 0 : match[0].length);
  376
+      }
  377
+      processModeInfo(value.substr(index), undefined);
  378
+      return {
  379
+        relevance: relevance,
  380
+        keyword_count: keyword_count,
  381
+        value: result,
  382
+        language: language_name
  383
+      };
  384
+    } catch (e) {
  385
+      if (e == 'Illegal') {
  386
+        return {
  387
+          relevance: 0,
  388
+          keyword_count: 0,
  389
+          value: escape(value)
  390
+        };
  391
+      } else {
  392
+        throw e;
  393
+      }
  394
+    }
  395
+  }
  396
+
  397
+  /*
  398
+  Highlighting with language detection. Accepts a string with the code to
  399
+  highlight. Returns an object with the following properties:
  400
+
  401
+  - language (detected language)
  402
+  - relevance (int)
  403
+  - keyword_count (int)
  404
+  - value (an HTML string with highlighting markup)
  405
+  - second_best (object with the same structure for second-best heuristically
  406
+    detected language, may be absent)
  407
+
  408
+  */
  409
+  function highlightAuto(text) {
  410
+    var result = {
  411
+      keyword_count: 0,
  412
+      relevance: 0,
  413
+      value: escape(text)
  414
+    };
  415
+    var second_best = result;
  416
+    for (var key in languages) {
  417
+      if (!languages.hasOwnProperty(key))
  418
+        continue;
  419
+      var current = highlight(key, text);
  420
+      current.language = key;
  421
+      if (current.keyword_count + current.relevance > second_best.keyword_count + second_best.relevance) {
  422
+        second_best = current;
  423
+      }
  424
+      if (current.keyword_count + current.relevance > result.keyword_count + result.relevance) {
  425
+        second_best = result;
  426
+        result = current;
  427
+      }
  428
+    }
  429
+    if (second_best.language) {
  430
+      result.second_best = second_best;
  431
+    }
  432
+    return result;
  433
+  }
  434
+
  435
+  /*
  436
+  Post-processing of the highlighted markup:
25 437
 
26  
-highlighter.highlightCodeBlocks = function (content) {
27  
-  var codeBlocks = content.getElementsByTagName('code')
28  
-    , block
29  
-    , i
30  
-    ;
  438
+  - replace TABs with something more useful
  439
+  - replace real line-breaks with '<br>' for non-pre containers
31 440
 
32  
-  for (i = 0; i < codeBlocks.length; i++) {
33  
-    block = codeBlocks[i];
  441
+  */
  442
+  function fixMarkup(value, tabReplace, useBR) {
  443
+    if (tabReplace) {
  444
+      value = value.replace(/^((<[^>]+>|\t)+)/gm, function(match, p1, offset, s) {
  445
+        return p1.replace(/\t/g, tabReplace);
  446
+      });
  447
+    }
  448
+    if (useBR) {
  449
+      value = value.replace(/\n/g, '<br>');
  450
+    }
  451
+    return value;
  452
+  }
  453
+
  454
+  /*
  455
+  Applies highlighting to a DOM node containing code. Accepts a DOM node and
  456
+  two optional parameters for fixMarkup.
  457
+  */
  458
+  function highlightBlock(block, tabReplace, useBR) {
  459
+    var text = blockText(block, useBR);
  460
+    var language = blockLanguage(block);
  461
+    if (language == 'no-highlight')
  462
+        return;
  463
+    var result = language ? highlight(language, text) : highlightAuto(text);
  464
+    language = result.language;
  465
+    var original = nodeStream(block);
  466
+    if (original.length) {
  467
+      var pre = document.createElement('pre');
  468
+      pre.innerHTML = result.value;
  469
+      result.value = mergeStreams(original, nodeStream(pre), text);
  470
+    }
  471
+    result.value = fixMarkup(result.value, tabReplace, useBR);
  472
+
  473
+    var class_name = block.className;
  474
+    if (!class_name.match('(\\s|^)(language-)?' + language + '(\\s|$)')) {
  475
+      class_name = class_name ? (class_name + ' ' + language) : language;
  476
+    }
  477
+    block.innerHTML = result.value;
  478
+    block.className = class_name;
  479
+    block.result = {
  480
+      language: language,
  481
+      kw: result.keyword_count,
  482
+      re: result.relevance
  483
+    };
  484
+    if (result.second_best) {
  485
+      block.second_best = {
  486
+        language: result.second_best.language,
  487
+        kw: result.second_best.keyword_count,
  488
+        re: result.second_best.relevance
  489
+      };
  490
+    }
  491
+  }
  492
+
  493
+  /*
  494
+  Applies highlighting to all <pre><code>..</code></pre> blocks on a page.
  495
+  */
  496
+  function initHighlighting() {
  497
+    if (initHighlighting.called)
  498
+      return;
  499
+    initHighlighting.called = true;
  500
+    Array.prototype.map.call(document.getElementsByTagName('pre'), findCode).
  501
+      filter(Boolean).
  502
+      forEach(function(code){highlightBlock(code, hljs.tabReplace)});
  503
+  }
  504
+
  505
+  /*
  506
+  Attaches highlighting to the page load event.
  507
+  */
  508
+  function initHighlightingOnLoad() {
  509
+    window.addEventListener('DOMContentLoaded', initHighlighting, false);
  510
+    window.addEventListener('load', initHighlighting, false);
  511
+  }
  512
+
  513
+  var languages = {}; // a shortcut to avoid writing "this." everywhere
  514
+
  515
+  /* Interface definition */
  516
+
  517
+  this.LANGUAGES = languages;
  518
+  this.highlight = highlight;
  519
+  this.highlightAuto = highlightAuto;
  520
+  this.fixMarkup = fixMarkup;
  521
+  this.highlightBlock = highlightBlock;
  522
+  this.initHighlighting = initHighlighting;
  523
+  this.initHighlightingOnLoad = initHighlightingOnLoad;
  524
+
  525
+  // Common regexps
  526
+  this.IDENT_RE = '[a-zA-Z][a-zA-Z0-9_]*';
  527
+  this.UNDERSCORE_IDENT_RE = '[a-zA-Z_][a-zA-Z0-9_]*';
  528
+  this.NUMBER_RE = '\\b\\d+(\\.\\d+)?';
  529
+  this.C_NUMBER_RE = '(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)'; // 0x..., 0..., decimal, float
  530
+  this.BINARY_NUMBER_RE = '\\b(0b[01]+)'; // 0b...
  531
+  this.RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|\\.|-|-=|/|/=|:|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~';
  532
+
  533
+  // Common modes
  534
+  this.BACKSLASH_ESCAPE = {
  535
+    begin: '\\\\[\\s\\S]', relevance: 0
  536
+  };
  537
+  this.APOS_STRING_MODE = {
  538
+    className: 'string',
  539
+    begin: '\'', end: '\'',
  540
+    illegal: '\\n',
  541
+    contains: [this.BACKSLASH_ESCAPE],
  542
+    relevance: 0
  543
+  };
  544
+  this.QUOTE_STRING_MODE = {
  545
+    className: 'string',
  546
+    begin: '"', end: '"',
  547
+    illegal: '\\n',
  548
+    contains: [this.BACKSLASH_ESCAPE],
  549
+    relevance: 0
  550
+  };
  551
+  this.C_LINE_COMMENT_MODE = {
  552
+    className: 'comment',
  553
+    begin: '//', end: '$'
  554
+  };
  555
+  this.C_BLOCK_COMMENT_MODE = {
  556
+    className: 'comment',
  557
+    begin: '/\\*', end: '\\*/'
  558
+  };
  559
+  this.HASH_COMMENT_MODE = {
  560
+    className: 'comment',
  561
+    begin: '#', end: '$'
  562
+  };
  563
+  this.NUMBER_MODE = {
  564
+    className: 'number',
  565
+    begin: this.NUMBER_RE,
  566
+    relevance: 0
  567
+  };
  568
+  this.C_NUMBER_MODE = {
  569
+    className: 'number',
  570
+    begin: this.C_NUMBER_RE,
  571
+    relevance: 0
  572
+  };
  573
+  this.BINARY_NUMBER_MODE = {
  574
+    className: 'number',
  575
+    begin: this.BINARY_NUMBER_RE,
  576
+    relevance: 0
  577
+  };
  578
+
  579
+  // Utility functions
  580
+  this.inherit = function(parent, obj) {
  581
+    var result = {}
  582
+    for (var key in parent)
  583
+      result[key] = parent[key];
  584
+    if (obj)
  585
+      for (var key in obj)
  586
+        result[key] = obj[key];
  587
+    return result;
  588
+  }
  589
+}
  590
+)()
  591
+  , languages = [{name:"javascript",create:/*
  592
+Language: JavaScript
  593
+*/
  594
+
  595
+function(hljs) {
  596
+  return {
  597
+    keywords: {
  598
+      keyword:
  599
+        'in if for while finally var new function do return void else break catch ' +
  600
+        'instanceof with throw case default try this switch continue typeof delete ' +
  601
+        'let yield const',
  602
+      literal:
  603
+        'true false null undefined NaN Infinity'
  604
+    },
  605
+    contains: [
  606
+      hljs.APOS_STRING_MODE,
  607
+      hljs.QUOTE_STRING_MODE,
  608
+      hljs.C_LINE_COMMENT_MODE,
  609
+      hljs.C_BLOCK_COMMENT_MODE,
  610
+      hljs.C_NUMBER_MODE,
  611
+      { // "value" container
  612
+        begin: '(' + hljs.RE_STARTERS_RE + '|\\b(case|return|throw)\\b)\\s*',
  613
+        keywords: 'return throw case',
  614
+        contains: [
  615
+          hljs.C_LINE_COMMENT_MODE,
  616
+          hljs.C_BLOCK_COMMENT_MODE,
  617
+          {
  618
+            className: 'regexp',
  619
+            begin: '/', end: '/[gim]*',
  620
+            illegal: '\\n',
  621
+            contains: [{begin: '\\\\/'}]
  622
+          },
  623
+          { // E4X
  624
+            begin: '<', end: '>;',
  625
+            subLanguage: 'xml'
  626
+          }
  627
+        ],
  628
+        relevance: 0
  629
+      },
  630
+      {
  631
+        className: 'function',
  632
+        beginWithKeyword: true, end: '{',
  633
+        keywords: 'function',
  634
+        contains: [
  635
+          {
  636
+            className: 'title', begin: '[A-Za-z$_][0-9A-Za-z$_]*'
  637
+          },
  638
+          {
  639
+            className: 'params',
  640
+            begin: '\\(', end: '\\)',
  641
+            contains: [
  642
+              hljs.C_LINE_COMMENT_MODE,
  643
+              hljs.C_BLOCK_COMMENT_MODE
  644
+            ],
  645
+            illegal: '["\'\\(]'
  646
+          }
  647
+        ],
  648
+        illegal: '\\[|%'
  649
+      }
  650
+    ]
  651
+  };
  652
+}
  653
+},{name:"ruby",create:/*
  654
+Language: Ruby
  655
+Author: Anton Kovalyov <anton@kovalyov.net>
  656
+Contributors: Peter Leonov <gojpeg@yandex.ru>, Vasily Polovnyov <vast@whiteants.net>, Loren Segal <lsegal@soen.ca>
  657
+*/
  658
+
  659
+function(hljs) {
  660
+  var RUBY_IDENT_RE = '[a-zA-Z_][a-zA-Z0-9_]*(\\!|\\?)?';
  661
+  var RUBY_METHOD_RE = '[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?';
  662
+  var RUBY_KEYWORDS = {
  663
+    keyword:
  664
+      'and false then defined module in return redo if BEGIN retry end for true self when ' +
  665
+      'next until do begin unless END rescue nil else break undef not super class case ' +
  666
+      'require yield alias while ensure elsif or include'
  667
+  };
  668
+  var YARDOCTAG = {
  669
+    className: 'yardoctag',
  670
+    begin: '@[A-Za-z]+'
  671
+  };
  672
+  var COMMENTS = [
  673
+    {
  674
+      className: 'comment',
  675
+      begin: '#', end: '$',
  676
+      contains: [YARDOCTAG]
  677
+    },
  678
+    {
  679
+      className: 'comment',
  680
+      begin: '^\\=begin', end: '^\\=end',
  681
+      contains: [YARDOCTAG],
  682
+      relevance: 10
  683
+    },
  684
+    {
  685
+      className: 'comment',
  686
+      begin: '^__END__', end: '\\n$'
  687
+    }
  688
+  ];
  689
+  var SUBST = {
  690
+    className: 'subst',
  691
+    begin: '#\\{', end: '}',
  692
+    lexems: RUBY_IDENT_RE,
  693
+    keywords: RUBY_KEYWORDS
  694
+  };
  695
+  var STR_CONTAINS = [hljs.BACKSLASH_ESCAPE, SUBST];
  696
+  var STRINGS = [
  697
+    {
  698
+      className: 'string',
  699
+      begin: '\'', end: '\'',
  700
+      contains: STR_CONTAINS,
  701
+      relevance: 0
  702
+    },
  703
+    {
  704
+      className: 'string',
  705
+      begin: '"', end: '"',
  706
+      contains: STR_CONTAINS,
  707
+      relevance: 0
  708
+    },
  709
+    {
  710
+      className: 'string',
  711
+      begin: '%[qw]?\\(', end: '\\)',
  712
+      contains: STR_CONTAINS
  713
+    },
  714
+    {
  715
+      className: 'string',
  716
+      begin: '%[qw]?\\[', end: '\\]',
  717
+      contains: STR_CONTAINS
  718
+    },
  719
+    {
  720
+      className: 'string',
  721
+      begin: '%[qw]?{', end: '}',
  722
+      contains: STR_CONTAINS
  723
+    },
  724
+    {
  725
+      className: 'string',
  726
+      begin: '%[qw]?<', end: '>',
  727
+      contains: STR_CONTAINS,
  728
+      relevance: 10
  729
+    },
  730
+    {
  731
+      className: 'string',
  732
+      begin: '%[qw]?/', end: '/',
  733
+      contains: STR_CONTAINS,
  734
+      relevance: 10
  735
+    },
  736
+    {
  737
+      className: 'string',
  738
+      begin: '%[qw]?%', end: '%',
  739
+      contains: STR_CONTAINS,
  740
+      relevance: 10
  741
+    },
  742
+    {
  743
+      className: 'string',
  744
+      begin: '%[qw]?-', end: '-',
  745
+      contains: STR_CONTAINS,
  746
+      relevance: 10
  747
+    },
  748
+    {
  749
+      className: 'string',
  750
+      begin: '%[qw]?\\|', end: '\\|',
  751
+      contains: STR_CONTAINS,
  752
+      relevance: 10
  753
+    }
  754
+  ];
  755
+  var FUNCTION = {
  756
+    className: 'function',
  757
+    beginWithKeyword: true, end: ' |$|;',
  758
+    keywords: 'def',
  759
+    contains: [
  760
+      {
  761
+        className: 'title',
  762
+        begin: RUBY_METHOD_RE,
  763
+        lexems: RUBY_IDENT_RE,
  764
+        keywords: RUBY_KEYWORDS
  765
+      },
  766
+      {
  767
+        className: 'params',
  768
+        begin: '\\(', end: '\\)',
  769
+        lexems: RUBY_IDENT_RE,
  770
+        keywords: RUBY_KEYWORDS
  771
+      }
  772
+    ].concat(COMMENTS)
  773
+  };
  774
+
  775
+  var RUBY_DEFAULT_CONTAINS = COMMENTS.concat(STRINGS.concat([
  776
+    {
  777
+      className: 'class',
  778
+      beginWithKeyword: true, end: '$|;',
  779
+      keywords: 'class module',
  780
+      contains: [
  781
+        {
  782
+          className: 'title',
  783
+          begin: '[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?',
  784
+          relevance: 0
  785
+        },
  786
+        {
  787
+          className: 'inheritance',
  788
+          begin: '<\\s*',
  789
+          contains: [{
  790
+            className: 'parent',
  791
+            begin: '(' + hljs.IDENT_RE + '::)?' + hljs.IDENT_RE
  792
+          }]
  793
+        }
  794
+      ].concat(COMMENTS)
  795
+    },
  796
+    FUNCTION,
  797
+    {
  798
+      className: 'constant',
  799
+      begin: '(::)?(\\b[A-Z]\\w*(::)?)+',
  800
+      relevance: 0
  801
+    },
  802
+    {
  803
+      className: 'symbol',
  804
+      begin: ':',
  805
+      contains: STRINGS.concat([{begin: RUBY_IDENT_RE}]),
  806
+      relevance: 0
  807
+    },
  808
+    {
  809
+      className: 'number',
  810
+      begin: '(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b',
  811
+      relevance: 0
  812
+    },
  813
+    {
  814
+      className: 'number',
  815
+      begin: '\\?\\w'
  816
+    },
  817
+    {
  818
+      className: 'variable',
  819
+      begin: '(\\$\\W)|((\\$|\\@\\@?)(\\w+))'
  820
+    },
  821
+    { // regexp container
  822
+      begin: '(' + hljs.RE_STARTERS_RE + ')\\s*',
  823
+      contains: COMMENTS.concat([
  824
+        {
  825
+          className: 'regexp',
  826
+          begin: '/', end: '/[a-z]*',
  827
+          illegal: '\\n',
  828
+          contains: [hljs.BACKSLASH_ESCAPE]
  829
+        }
  830
+      ]),
  831
+      relevance: 0
  832
+    }
  833
+  ]));
  834
+  SUBST.contains = RUBY_DEFAULT_CONTAINS;
  835
+  FUNCTION.contains[1].contains = RUBY_DEFAULT_CONTAINS;
  836
+
  837
+  return {
  838
+    lexems: RUBY_IDENT_RE,
  839
+    keywords: RUBY_KEYWORDS,
  840
+    contains: RUBY_DEFAULT_CONTAINS
  841
+  };
  842
+}
  843
+},{name:"python",create:/*
  844
+Language: Python
  845
+*/
  846
+
  847
+function(hljs) {
  848
+  var STRINGS = [
  849
+    {
  850
+      className: 'string',
  851
+      begin: '(u|b)?r?\'\'\'', end: '\'\'\'',
  852
+      relevance: 10
  853
+    },
  854
+    {
  855
+      className: 'string',
  856
+      begin: '(u|b)?r?"""', end: '"""',
  857
+      relevance: 10
  858
+    },
  859
+    {
  860
+      className: 'string',
  861
+      begin: '(u|r|ur)\'', end: '\'',
  862
+      contains: [hljs.BACKSLASH_ESCAPE],
  863
+      relevance: 10
  864
+    },
  865
+    {
  866
+      className: 'string',
  867
+      begin: '(u|r|ur)"', end: '"',
  868
+      contains: [hljs.BACKSLASH_ESCAPE],
  869
+      relevance: 10
  870
+    },
  871
+    {
  872
+      className: 'string',
  873
+      begin: '(b|br)\'', end: '\'',
  874
+      contains: [hljs.BACKSLASH_ESCAPE]
  875
+    },
  876
+    {
  877
+      className: 'string',
  878
+      begin: '(b|br)"', end: '"',
  879
+      contains: [hljs.BACKSLASH_ESCAPE]
  880
+    }
  881
+  ].concat([
  882
+    hljs.APOS_STRING_MODE,
  883
+    hljs.QUOTE_STRING_MODE
  884
+  ]);
  885
+  var TITLE = {
  886
+    className: 'title', begin: hljs.UNDERSCORE_IDENT_RE
  887
+  };
  888
+  var PARAMS = {
  889
+    className: 'params',
  890
+    begin: '\\(', end: '\\)',
  891
+    contains: ['self', hljs.C_NUMBER_MODE].concat(STRINGS)
  892
+  };
  893
+  var FUNC_CLASS_PROTO = {
  894
+    beginWithKeyword: true, end: ':',
  895
+    illegal: '[${=;\\n]',
  896
+    contains: [TITLE, PARAMS],
  897
+    relevance: 10
  898
+  };
  899
+
  900
+  return {
  901
+    keywords: {
  902
+      keyword:
  903
+        'and elif is global as in if from raise for except finally print import pass return ' +
  904
+        'exec else break not with class assert yield try while continue del or def lambda ' +
  905
+        'nonlocal|10',
  906
+      built_in:
  907
+        'None True False Ellipsis NotImplemented'
  908
+    },
  909
+    illegal: '(</|->|\\?)',
  910
+    contains: STRINGS.concat([
  911
+      hljs.HASH_COMMENT_MODE,
  912
+      hljs.inherit(FUNC_CLASS_PROTO, {className: 'function', keywords: 'def'}),
  913
+      hljs.inherit(FUNC_CLASS_PROTO, {className: 'class', keywords: 'class'}),
  914
+      hljs.C_NUMBER_MODE,
  915
+      {
  916
+        className: 'decorator',
  917
+        begin: '@', end: '$'
  918
+      },
  919
+      {
  920
+        begin: '\\b(print|exec)\\(' // don’t highlight keywords-turned-functions in Python 3
  921
+      }
  922
+    ])
  923
+  };
  924
+}
  925
+},{name:"bash",create:/*
  926
+Language: Bash
  927
+Author: vah <vahtenberg@gmail.com>
  928
+*/
  929
+
  930
+function(hljs) {
  931
+  var BASH_LITERAL = 'true false';
  932
+  var VAR1 = {
  933
+    className: 'variable', begin: '\\$[a-zA-Z0-9_]+\\b'
  934
+  };
  935
+  var VAR2 = {
  936
+    className: 'variable', begin: '\\${([^}]|\\\\})+}'
  937
+  };
  938
+  var QUOTE_STRING = {
  939
+    className: 'string',
  940
+    begin: '"', end: '"',
  941
+    illegal: '\\n',
  942
+    contains: [hljs.BACKSLASH_ESCAPE, VAR1, VAR2],
  943
+    relevance: 0
  944
+  };
  945
+  var APOS_STRING = {
  946
+    className: 'string',
  947
+    begin: '\'', end: '\'',
  948
+    contains: [{begin: '\'\''}],
  949
+    relevance: 0
  950
+  };
  951
+  var TEST_CONDITION = {
  952
+    className: 'test_condition',
  953
+    begin: '', end: '',
  954
+    contains: [QUOTE_STRING, APOS_STRING, VAR1, VAR2],
  955
+    keywords: {
  956
+      literal: BASH_LITERAL
  957
+    },
  958
+    relevance: 0
  959
+  };
  960
+
  961
+  return {
  962
+    keywords: {
  963
+      keyword: 'if then else fi for break continue while in do done echo exit return set declare',
  964
+      literal: BASH_LITERAL
  965
+    },
  966
+    contains: [
  967
+      {
  968
+        className: 'shebang',
  969
+        begin: '(#!\\/bin\\/bash)|(#!\\/bin\\/sh)',
  970
+        relevance: 10
  971
+      },
  972
+      VAR1,
  973
+      VAR2,
  974
+      hljs.HASH_COMMENT_MODE,
  975
+      QUOTE_STRING,
  976
+      APOS_STRING,
  977
+      hljs.inherit(TEST_CONDITION, {begin: '\\[ ', end: ' \\]', relevance: 0}),
  978
+      hljs.inherit(TEST_CONDITION, {begin: '\\[\\[ ', end: ' \\]\\]'})
  979
+    ]
  980
+  };
  981
+}
  982
+},{name:"java",create:/*
  983
+Language: Java
  984
+Author: Vsevolod Solovyov <vsevolod.solovyov@gmail.com>
  985
+*/
  986
+
  987
+function(hljs) {
  988
+  return {
  989
+    keywords:
  990
+      'false synchronized int abstract float private char boolean static null if const ' +
  991
+      'for true while long throw strictfp finally protected import native final return void ' +
  992
+      'enum else break transient new catch instanceof byte super volatile case assert short ' +
  993
+      'package default double public try this switch continue throws',
  994
+    contains: [
  995
+      {
  996
+        className: 'javadoc',
  997
+        begin: '/\\*\\*', end: '\\*/',
  998
+        contains: [{
  999
+          className: 'javadoctag', begin: '@[A-Za-z]+'
  1000
+        }],
  1001
+        relevance: 10
  1002
+      },
  1003
+      hljs.C_LINE_COMMENT_MODE,
  1004
+      hljs.C_BLOCK_COMMENT_MODE,
  1005
+      hljs.APOS_STRING_MODE,
  1006
+      hljs.QUOTE_STRING_MODE,
  1007
+      {
  1008
+        className: 'class',
  1009
+        beginWithKeyword: true, end: '{',
  1010
+        keywords: 'class interface',
  1011
+        illegal: ':',
  1012
+        contains: [
  1013
+          {
  1014
+            beginWithKeyword: true,
  1015
+            keywords: 'extends implements',
  1016
+            relevance: 10