forked from thlorenz/doctoc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdoctoc.js
147 lines (114 loc) · 3.95 KB
/
doctoc.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
#!/usr/bin/env node
var path = require('path'),
fs = require('fs'),
_ = require('underscore'),
file = require('./lib/file'),
argv = process.argv;
if (argv.length !== 3) {
console.log('Usage: %s path (where path is some path e.g., ".")', argv[0] + ' ' + argv[1]);
process.exit(0);
}
var target = cleanPath(argv[2]);
console.log ('\nDocToccing "%s" and its sub directories.', target);
var files = file.findMarkdownFiles(target);
transformAndSave(files);
console.log('\nEverything is OK.');
// ------ Supporting functions ----------
function cleanPath(path) {
var homeExpanded = (path.indexOf('~') === 0) ? process.env.HOME + path.substr(1) : path;
// Escape all spaces
return homeExpanded.replace(/\s/g, '\\ ');
}
function notNull(x) { return x !== null; }
function addLink(header) {
return _(header).extend({
link: '#' + header.name.trim().toLowerCase()
.replace(/ /g,'-')
.replace(/[`.,()*]/g,'')
});
}
function getHashedHeaders (_lines) {
// Find headers of the form '### xxxx xxx xx'
return _lines
.map(function (x, index) {
var match = /^(\#{1,8})[ ]*(.+)$/.exec(x);
return match ? {
rank : match[1].length,
name : 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 (/^==+/.exec(line)) rank = 1;
else if (/^--+/.exec(line)) rank = 2;
else return null;
return {
rank : rank,
name : lines[index - 1],
line : index - 1
};
})
.filter(notNull)
.value();
}
function transform (f, content) {
var lines = content.split('\n'),
_lines = _(lines).chain(),
allHeaders = getHashedHeaders(_lines).concat(getUnderlinedHeaders(_lines)),
lowestRank = _(allHeaders).chain().pluck('rank').min().value(),
linkedHeaders = _(allHeaders).map(addLink);
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.name + '](' + x.link + ')';
})
.join('\n') +
'\n';
var currentToc = _lines
.first(linkedHeaders[0].line)
.value()
.join('\n');
if (currentToc === toc) {
console.log('"%s" is up to date', f.path);
return { transformed: false };
}
console.log('"%s" will be updated', f.path);
// 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,
path : f.path
};
}
function transformAndSave(files) {
console.log('\n==================\n');
_(files)
.chain()
.map(function (x) {
var content = fs.readFileSync(x.path, 'utf8');
return transform(x, content);
})
.filter(function (x) { return x.transformed; })
.each(function (x) {
fs.writeFileSync(x.path, x.data, 'utf8');
});
}