summaryrefslogtreecommitdiffstats
path: root/lib/parse/page.js
blob: 75eca9db0f28d5f037467e644b084d4c60d0c36e (plain)
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
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) {
    // 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;