UNPKG

3.81 kBJavaScriptView Raw
1const parseMarkdown = require('./parse_markdown');
2const chalk = require('chalk');
3const path = require('path');
4const fs = require('fs');
5
6/**
7 * Sort two documentation objects, given an optional order object. Returns
8 * a numeric sorting value that is compatible with stream-sort.
9 *
10 * @param {Array<Object>} comments all comments
11 * @param {Object} options options from documentation.yml
12 * @returns {number} sorting value
13 * @private
14 */
15module.exports = function sortDocs(comments, options) {
16 if (!options || !options.toc) {
17 return sortComments(comments, options && options.sortOrder);
18 }
19 let i = 0;
20 const indexes = Object.create(null);
21 const toBeSorted = Object.create(null);
22 const paths = Object.create(null);
23 const fixed = [];
24 const walk = function(tocPath, val) {
25 if (typeof val === 'object' && val.name) {
26 val.kind = 'note';
27 indexes[val.name] = i++;
28 if (typeof val.file === 'string') {
29 let filename = val.file;
30 if (!path.isAbsolute(val.file)) {
31 filename = path.join(process.cwd(), val.file);
32 }
33
34 try {
35 val.description = fs.readFileSync(filename).toString();
36 delete val.file;
37 } catch (err) {
38 process.stderr.write(chalk.red(`Failed to read file ${filename}`));
39 }
40 } else if (!val.description) {
41 val.description = '';
42 }
43 if (typeof val.description === 'string') {
44 val.description = parseMarkdown(val.description);
45 }
46 const childPath = tocPath.concat({ scope: 'static', name: val.name });
47 val.path = childPath;
48 if (val.children) {
49 val.children.forEach(walk.bind(null, childPath));
50 }
51 fixed.push(val);
52 } else {
53 indexes[val] = i++;
54 toBeSorted[val] = false;
55 paths[val] = tocPath.concat({ scope: 'static', name: val, toc: true });
56 }
57 };
58 // Table of contents 'theme' entries: defined as objects
59 // in the YAML list
60 options.toc.forEach(walk.bind(null, []));
61 const unfixed = [];
62 comments.forEach(function(comment) {
63 let commentPath;
64 if (!comment.memberof && (commentPath = paths[comment.name])) {
65 comment.path = commentPath;
66 delete paths[comment.name];
67 }
68
69 // If comment is of kind 'note', this means that we must be _re_ sorting
70 // the list, and the TOC note entries were already added to the list. Bail
71 // out here so that we don't add duplicates.
72 if (comment.kind === 'note') {
73 return;
74 }
75
76 // If comment is top-level and `name` matches a TOC entry, add it to the
77 // to-be-sorted list.
78 if (!comment.memberof && indexes[comment.name] !== undefined) {
79 fixed.push(comment);
80 toBeSorted[comment.name] = true;
81 } else {
82 unfixed.push(comment);
83 }
84 });
85 fixed.sort((a, b) => {
86 if (indexes[a.name] !== undefined && indexes[b.name] !== undefined) {
87 return indexes[a.name] - indexes[b.name];
88 }
89 return 0;
90 });
91 sortComments(unfixed, options.sortOrder);
92 Object.keys(toBeSorted)
93 .filter(key => toBeSorted[key] === false)
94 .forEach(key => {
95 process.stderr.write(
96 chalk.red(
97 'Table of contents defined sorting of ' +
98 key +
99 ' but no documentation with that namepath was found\n'
100 )
101 );
102 });
103 return fixed.concat(unfixed);
104};
105
106function compareCommentsByName(a, b) {
107 const akey = a.name;
108 const bkey = b.name;
109
110 if (akey && bkey) {
111 return akey.localeCompare(bkey, undefined, { caseFirst: 'upper' });
112 }
113 return 0;
114}
115
116function compareCommentsBySourceLocation(a, b) {
117 return a.context.sortKey.localeCompare(b.context.sortKey);
118}
119
120function sortComments(comments, sortOrder) {
121 return comments.sort(
122 sortOrder === 'alpha'
123 ? compareCommentsByName
124 : compareCommentsBySourceLocation
125 );
126}