var _ = require('lodash'); var marked = require('marked'); var hljs = require('highlight.js'); var renderer = require('./renderer'); var lnormalize = require('../utils/lang').normalize; // Synchronous highlighting with highlight.js marked.setOptions({ highlight: function (code, lang) { try { return hljs.highlight(lang, code).value; } catch(e) { return hljs.highlightAuto(code).value; } } }); // Split a page up into sections (lesson, exercises, ...) function splitSections(nodes) { var section = []; return _.reduce(nodes, function(sections, el) { if(el.type === 'hr') { sections.push(section); section = []; } else { section.push(el); } return sections; }, []).concat([section]); // Add remaining nodes } // What is the type of this section function sectionType(nodes, idx) { var codeNodes = _.filter(nodes, { type: 'code' }).length; if(codeNodes === 3 && (idx % 2) == 1) { return 'exercise'; } return 'normal'; } // Render a section using our custom renderer function render(section, _options) { // Copy section section = _.toArray(section); // marked's Render expects this, we don't use it yet section.links = {}; // Build options using defaults and our custom renderer var options = _.extend({}, marked.defaults, { renderer: renderer(null, _options) }); return marked.parser(section, options); } function parsePage(src, options) { options = options || {}; // Lex file var nodes = marked.lexer(src); return _.chain(splitSections(nodes)) .map(function(section, idx) { // Detect section type section.type = sectionType(section, idx); return section; }) .filter(function(section) { return !_.isEmpty(section); }) .reduce(function(sections, section) { var last = _.last(sections); // Merge normal sections together if(last && last.type === section.type && last.type === 'normal') { last.push.apply(last, [{'type': 'hr'}].concat(section)); } else { // Add to list of sections sections.push(section); } return sections; }, []) .map(function(section) { // Generate a uniqueId to identify this section in our code var id = _.uniqueId('gitbook_'); // Transform given type if(section.type === 'exercise') { var nonCodeNodes = _.reject(section, { 'type': 'code' }); var codeNodes = _.filter(section, { 'type': 'code' }); // Languages in code blocks var langs = _.pluck(codeNodes, 'lang').map(lnormalize); // Check that they are all the same var validLangs = _.all(_.map(langs, function(lang) { return lang && lang === langs[0]; })); // Main language var lang = validLangs ? langs[0] : null; return { id: id, type: section.type, content: render(nonCodeNodes), lang: lang, code: { base: codeNodes[0].text, solution: codeNodes[1].text, validation: codeNodes[2].text, } }; } // Render normal pages return { id: id, type: section.type, content: render(section, options) }; }) .value(); } // Exports module.exports = parsePage;