|
1 |
| -/** |
2 |
| - * @author Titus Wormer |
3 |
| - * @copyright 2015 Titus Wormer |
4 |
| - * @license MIT |
5 |
| - * @module mdast:util:heading-range |
6 |
| - * @fileoverview Markdown heading as ranges in mdast. |
7 |
| - */ |
8 |
| - |
9 | 1 | 'use strict';
|
10 | 2 |
|
11 |
| -/* eslint-env commonjs */ |
12 |
| - |
13 |
| -/* |
14 |
| - * Dependencies. |
15 |
| - */ |
16 |
| - |
| 3 | +/* Dependencies. */ |
17 | 4 | var toString = require('mdast-util-to-string');
|
18 | 5 |
|
19 |
| -/* |
20 |
| - * Methods. |
21 |
| - */ |
| 6 | +/* Expose. */ |
| 7 | +module.exports = headingRange; |
22 | 8 |
|
| 9 | +/* Methods. */ |
23 | 10 | var splice = [].splice;
|
24 | 11 |
|
25 |
| -/** |
26 |
| - * Transform a string into an applicable expression. |
27 |
| - * |
28 |
| - * @param {string} value - Value to transform. |
29 |
| - * @return {RegExp} - Expression. |
30 |
| - */ |
31 |
| -function toExpression(value) { |
32 |
| - return new RegExp('^(' + value + ')$', 'i'); |
33 |
| -} |
34 |
| - |
35 |
| -/** |
36 |
| - * Wrap an expression into an assertion function. |
37 |
| - * |
38 |
| - * @param {RegExp} expression - Expression to test. |
39 |
| - * @return {Function} - Assertion. |
40 |
| - */ |
41 |
| -function wrapExpression(expression) { |
42 |
| - /** |
43 |
| - * Assert `value` matches the bound `expression`. |
44 |
| - * |
45 |
| - * @param {string} value - Value to check. |
46 |
| - * @return {boolean} - Whether `value` matches. |
47 |
| - */ |
48 |
| - function assertion(value) { |
49 |
| - return expression.test(value); |
50 |
| - } |
51 |
| - |
52 |
| - return assertion; |
53 |
| -} |
| 12 | +/* Search `node` for `heading` and invoke `callback`. */ |
| 13 | +function headingRange(node, heading, callback) { |
| 14 | + var test = heading; |
54 | 15 |
|
55 |
| -/** |
56 |
| - * Check if `node` is a heading. |
57 |
| - * |
58 |
| - * @param {Node} node - Node to check. |
59 |
| - * @return {boolean} - Whether `node` is a heading. |
60 |
| - */ |
61 |
| -function isHeading(node) { |
62 |
| - return node && node.type === 'heading'; |
63 |
| -} |
| 16 | + if (typeof test === 'string') { |
| 17 | + test = toExpression(test); |
| 18 | + } |
64 | 19 |
|
65 |
| -/** |
66 |
| - * Check if `node` is the main heading. |
67 |
| - * |
68 |
| - * @param {Node} node - Node to check. |
69 |
| - * @param {number?} depth - Depth to search for. |
70 |
| - * @param {function(string): boolean} test - Tester. |
71 |
| - * @return {boolean} - Whether `node` is an opening |
72 |
| - * heading. |
73 |
| - */ |
74 |
| -function isOpeningHeading(node, depth, test) { |
75 |
| - return depth === null && isHeading(node) && test(toString(node), node); |
76 |
| -} |
| 20 | + if ('test' in test) { |
| 21 | + test = wrapExpression(test); |
| 22 | + } |
77 | 23 |
|
78 |
| -/** |
79 |
| - * Check if `node` is the next heading. |
80 |
| - * |
81 |
| - * @param {Node} node - Node to check. |
82 |
| - * @param {number?} depth - Depth of the opening heading. |
83 |
| - * @return {boolean} - Whether `node` is a closing |
84 |
| - * heading. |
85 |
| - */ |
86 |
| -function isClosingHeading(node, depth) { |
87 |
| - return depth && isHeading(node) && node.depth <= depth; |
| 24 | + search(node, test, callback); |
88 | 25 | }
|
89 | 26 |
|
90 |
| -/** |
91 |
| - * Search a node for heading range. |
92 |
| - * |
93 |
| - * @param {Node} root - Node to search. |
94 |
| - * @param {Function} test - Assertion. |
95 |
| - * @param {Function} callback - Callback invoked when |
96 |
| - * found. |
97 |
| - */ |
| 27 | +/* Search a node for heading range. */ |
98 | 28 | function search(root, test, callback) {
|
99 |
| - var index = -1; |
100 |
| - var children = root.children; |
101 |
| - var length = children.length; |
102 |
| - var depth = null; |
103 |
| - var start = null; |
104 |
| - var end = null; |
105 |
| - var nodes; |
106 |
| - var clean; |
107 |
| - var child; |
| 29 | + var index = -1; |
| 30 | + var children = root.children; |
| 31 | + var length = children.length; |
| 32 | + var depth = null; |
| 33 | + var start = null; |
| 34 | + var end = null; |
| 35 | + var nodes; |
| 36 | + var clean; |
| 37 | + var child; |
| 38 | + |
| 39 | + while (++index < length) { |
| 40 | + child = children[index]; |
| 41 | + |
| 42 | + if (isClosingHeading(child, depth)) { |
| 43 | + end = index; |
| 44 | + break; |
| 45 | + } |
108 | 46 |
|
109 |
| - while (++index < length) { |
110 |
| - child = children[index]; |
| 47 | + if (isOpeningHeading(child, depth, test)) { |
| 48 | + start = index; |
| 49 | + depth = child.depth; |
| 50 | + } |
| 51 | + } |
111 | 52 |
|
112 |
| - if (isClosingHeading(child, depth)) { |
113 |
| - end = index; |
114 |
| - break; |
115 |
| - } |
| 53 | + if (start !== null) { |
| 54 | + if (end === null) { |
| 55 | + end = length + 1; |
| 56 | + } |
116 | 57 |
|
117 |
| - if (isOpeningHeading(child, depth, test)) { |
118 |
| - start = index; |
119 |
| - depth = child.depth; |
120 |
| - } |
| 58 | + nodes = callback( |
| 59 | + children[start], |
| 60 | + children.slice(start + 1, end), |
| 61 | + children[end], |
| 62 | + { |
| 63 | + parent: root, |
| 64 | + start: start, |
| 65 | + end: end in children ? end : null |
| 66 | + } |
| 67 | + ); |
| 68 | + |
| 69 | + clean = []; |
| 70 | + index = -1; |
| 71 | + length = nodes && nodes.length; |
| 72 | + |
| 73 | + /* Ensure no empty nodes are inserted. This could |
| 74 | + * be the case if `end` is in `nodes` but no `end` |
| 75 | + * node exists. */ |
| 76 | + while (++index < length) { |
| 77 | + if (nodes[index]) { |
| 78 | + clean.push(nodes[index]); |
| 79 | + } |
121 | 80 | }
|
122 | 81 |
|
123 |
| - if (start !== null) { |
124 |
| - if (end === null) { |
125 |
| - end = length + 1; |
126 |
| - } |
127 |
| - |
128 |
| - nodes = callback( |
129 |
| - children[start], |
130 |
| - children.slice(start + 1, end), |
131 |
| - children[end], |
132 |
| - { |
133 |
| - 'parent': root, |
134 |
| - 'start': start, |
135 |
| - 'end': end in children ? end : null |
136 |
| - } |
137 |
| - ); |
138 |
| - |
139 |
| - clean = []; |
140 |
| - index = -1; |
141 |
| - length = nodes && nodes.length; |
142 |
| - |
143 |
| - /* |
144 |
| - * Ensure no empty nodes are inserted. This could |
145 |
| - * be the case if `end` is in `nodes` but no `end` |
146 |
| - * node exists. |
147 |
| - */ |
148 |
| - |
149 |
| - while (++index < length) { |
150 |
| - if (nodes[index]) { |
151 |
| - clean.push(nodes[index]); |
152 |
| - } |
153 |
| - } |
154 |
| - |
155 |
| - if (nodes) { |
156 |
| - splice.apply(children, [start, end - start + 1].concat(clean)); |
157 |
| - } |
| 82 | + if (nodes) { |
| 83 | + splice.apply(children, [start, end - start + 1].concat(clean)); |
158 | 84 | }
|
| 85 | + } |
159 | 86 | }
|
160 | 87 |
|
161 |
| -/** |
162 |
| - * Search `node` for `heading` and invoke `callback`. |
163 |
| - * |
164 |
| - * @param {Node} node - Node to search in. |
165 |
| - * @param {string|RegExp|Function} heading - Heading to |
166 |
| - * search for. |
167 |
| - * @param {Function} callback - Callback invoked when |
168 |
| - * found. |
169 |
| - */ |
170 |
| -function headingRange(node, heading, callback) { |
171 |
| - var test = heading; |
| 88 | +/* Transform a string into an applicable expression. */ |
| 89 | +function toExpression(value) { |
| 90 | + return new RegExp('^(' + value + ')$', 'i'); |
| 91 | +} |
172 | 92 |
|
173 |
| - if (typeof test === 'string') { |
174 |
| - test = toExpression(test); |
175 |
| - } |
| 93 | +/* Wrap an expression into an assertion function. */ |
| 94 | +function wrapExpression(expression) { |
| 95 | + return assertion; |
176 | 96 |
|
177 |
| - if ('test' in test) { |
178 |
| - test = wrapExpression(test); |
179 |
| - } |
| 97 | + /* Assert `value` matches the bound `expression`. */ |
| 98 | + function assertion(value) { |
| 99 | + return expression.test(value); |
| 100 | + } |
| 101 | +} |
180 | 102 |
|
181 |
| - search(node, test, callback); |
| 103 | +/* Check if `node` is a heading. */ |
| 104 | +function isHeading(node) { |
| 105 | + return node && node.type === 'heading'; |
182 | 106 | }
|
183 | 107 |
|
184 |
| -/* |
185 |
| - * Expose. |
186 |
| - */ |
| 108 | +/* Check if `node` is the main heading. */ |
| 109 | +function isOpeningHeading(node, depth, test) { |
| 110 | + return depth === null && isHeading(node) && test(toString(node), node); |
| 111 | +} |
187 | 112 |
|
188 |
| -module.exports = headingRange; |
| 113 | +/* Check if `node` is the next heading. */ |
| 114 | +function isClosingHeading(node, depth) { |
| 115 | + return depth && isHeading(node) && node.depth <= depth; |
| 116 | +} |
0 commit comments