forked from thlorenz/doctoc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
transform.js
132 lines (103 loc) · 3.11 KB
/
transform.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
'use strict';
var _ = require('underscore')
, anchor = require('anchor-markdown-header');
function notNull(x) { return x !== null; }
function addAnchor(mode, header) {
header.anchor = anchor(header.name, mode, header.instance);
return header;
}
function getHashedHeaders (_lines) {
var inCodeBlock = false;
// Turn all headers into '## xxx' even if they were '## xxx ##'
function normalize(header) {
return header.replace(/[ #]+$/, '');
}
// Find headers of the form '### xxxx xxx xx [###]'
return _lines
.filter(function (x) {
if (x.match(/^```/)) {
inCodeBlock = !inCodeBlock;
}
return !inCodeBlock;
})
.map(function (x, index) {
var match = /^(\#{1,8})[ ]*(.+)\r?$/.exec(x);
return match
? { rank : match[1].length
, name : normalize(match[2])
, line : index
}
: null;
})
.filter(notNull)
.value();
}
function getUnderlinedHeaders (_lines) {
// Find headers of the form
// h1 h2
// == --
return _lines
.map(function (line, index, lines) {
if (index === 0) return null;
var rank;
if (/^==+ *\r?$/.exec(line)) rank = 1;
else if (/^--+ *\r?$/.exec(line)) rank = 2;
else return null;
return {
rank : rank,
name : lines[index - 1],
line : index - 1
};
})
.filter(notNull)
.value();
}
function countHeaders (headers) {
var instances = {};
for (var i = 0; i < headers.length; i++) {
var header = headers[i];
var name = header.name;
if (instances.hasOwnProperty(name)) {
instances[name]++;
} else {
instances[name] = 0;
}
header.instance = instances[name];
}
return headers;
}
module.exports = function transform(content, mode) {
var lines = content.split('\n')
, _lines = _(lines).chain();
var headers = getHashedHeaders(_lines).concat(getUnderlinedHeaders(_lines));
headers.sort(function (a, b) {
return a.line - b.line;
});
var allHeaders = countHeaders(headers)
, lowestRank = _(allHeaders).chain().pluck('rank').min().value()
, linkedHeaders = _(allHeaders).map(addAnchor.bind(null, mode));
if (linkedHeaders.length === 0) return { transformed: false };
var toc =
'**Table of Contents** *generated with [DocToc](http://doctoc.herokuapp.com/)*'
+ '\n\n'
+ linkedHeaders
.map(function (x) {
var indent = _(_.range(x.rank - lowestRank))
.reduce(function (acc, x) { return acc + '\t'; }, '');
return indent + '- ' + x.anchor;
})
.join('\n')
+ '\n';
var currentToc = _lines
.first(linkedHeaders[0].line)
.value()
.join('\n');
if (currentToc === toc) return { transformed: false };
// Skip all lines up to first header since that is the old table of content
var remainingContent = _lines
.rest(linkedHeaders[0].line)
.value()
.join('\n');
var data = toc + '\n' + remainingContent;
return { transformed : true, data : data };
};