Skip to content

Commit 9ec5500

Browse files
committed
Add support for ignoring final definitions in a section
Related to remarkjs/remark-license#7.
1 parent 05cba18 commit 9ec5500

File tree

3 files changed

+157
-38
lines changed

3 files changed

+157
-38
lines changed

index.js

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,36 @@
11
'use strict';
22

3-
/* Dependencies. */
43
var toString = require('mdast-util-to-string');
54

6-
/* Expose. */
75
module.exports = headingRange;
86

9-
/* Methods. */
107
var splice = [].splice;
118

12-
/* Search `node` for `heading` and invoke `callback`. */
13-
function headingRange(node, heading, callback) {
14-
var test = heading;
9+
/* Search `node` with `options` and invoke `callback`. */
10+
function headingRange(node, options, callback) {
11+
var test = options;
12+
var ignoreFinalDefinitions = false;
13+
14+
/* Object, not regex. */
15+
if (test && typeof test === 'object' && !('exec' in test)) {
16+
ignoreFinalDefinitions = test.ignoreFinalDefinitions === true;
17+
test = test.test;
18+
}
1519

1620
if (typeof test === 'string') {
1721
test = toExpression(test);
1822
}
1923

20-
if ('test' in test) {
24+
/* Regex */
25+
if ('exec' in test) {
2126
test = wrapExpression(test);
2227
}
2328

24-
search(node, test, callback);
29+
search(node, test, ignoreFinalDefinitions, callback);
2530
}
2631

2732
/* Search a node for heading range. */
28-
function search(root, test, callback) {
33+
function search(root, test, skip, callback) {
2934
var index = -1;
3035
var children = root.children;
3136
var length = children.length;
@@ -39,20 +44,32 @@ function search(root, test, callback) {
3944
while (++index < length) {
4045
child = children[index];
4146

42-
if (isClosingHeading(child, depth)) {
47+
if (closing(child, depth)) {
4348
end = index;
4449
break;
4550
}
4651

47-
if (isOpeningHeading(child, depth, test)) {
52+
if (opening(child, depth, test)) {
4853
start = index;
4954
depth = child.depth;
5055
}
5156
}
5257

5358
if (start !== null) {
5459
if (end === null) {
55-
end = length + 1;
60+
end = length;
61+
}
62+
63+
if (skip) {
64+
while (end > start) {
65+
child = children[end - 1];
66+
67+
if (!definition(child)) {
68+
break;
69+
}
70+
71+
end--;
72+
}
5673
}
5774

5875
nodes = callback(
@@ -62,7 +79,7 @@ function search(root, test, callback) {
6279
{
6380
parent: root,
6481
start: start,
65-
end: end in children ? end : null
82+
end: children[end] ? end : null
6683
}
6784
);
6885

@@ -101,16 +118,20 @@ function wrapExpression(expression) {
101118
}
102119

103120
/* Check if `node` is a heading. */
104-
function isHeading(node) {
121+
function heading(node) {
105122
return node && node.type === 'heading';
106123
}
107124

108125
/* Check if `node` is the main heading. */
109-
function isOpeningHeading(node, depth, test) {
110-
return depth === null && isHeading(node) && test(toString(node), node);
126+
function opening(node, depth, test) {
127+
return depth === null && heading(node) && test(toString(node), node);
111128
}
112129

113130
/* Check if `node` is the next heading. */
114-
function isClosingHeading(node, depth) {
115-
return depth && isHeading(node) && node.depth <= depth;
131+
function closing(node, depth) {
132+
return depth && heading(node) && node.depth <= depth;
133+
}
134+
135+
function definition(node) {
136+
return node.type === 'definition' || node.type === 'footnoteDefinition';
116137
}

readme.md

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -63,33 +63,44 @@ Qux.
6363

6464
## API
6565

66-
### `heading(node, test, onrun)`
66+
### `heading(tree, test|options, onrun)`
6767

68-
Transform part of a document without affecting other parts, by changing
69-
a section: a heading which passes `test`, until the next heading of the
70-
same or lower depth, or the end of the document.
68+
Search `tree` ([`Node`][node]) and transform a section without affecting other
69+
parts with `onrun` ([`Function`][onrun]).
70+
A Section is a heading that passes `test`, until the next heading of the same
71+
or lower depth, or the end of the document. If `ignoreFinalDefinitions: true`,
72+
final definitions “in” the section are excluded.
7173

72-
###### Parameters
74+
###### `options`
75+
76+
* `test` (`string`, `RegExp`, [`Function`][test])
77+
— Heading to look for.
78+
When `string`, wrapped in `new RegExp('^(' + value + ')$', 'i')`;
79+
when `RegExp`, wrapped in `function (value) {expression.test(value)}`
80+
* `ignoreFinalDefinitions` (`boolean`, default: `false`)
81+
— Ignore final definitions otherwise in the section
82+
83+
#### `function test(value, node)`
84+
85+
Function invoked for each heading with its content (`string`) and `node`
86+
itself ([`Heading`][heading]) to check if it’s the one to look for.
7387

74-
* `node` ([`Node`][node]) — Node to search
75-
* `test` (`string`, `RegExp`, `function(string, Node): boolean`)
76-
— Heading to look for. When `string`, wrapped in
77-
`new RegExp('^(' + value + ')$', 'i')`; when `RegExp`, wrapped
78-
in `function (value) {expression.test(value)}`
79-
* `onrun` ([`Function`][onrun])
88+
###### Returns
89+
90+
`Boolean?`, `true` if this is the heading to use.
8091

8192
#### `function onrun(start, nodes, end?, scope)`
8293

8394
Callback invoked when a range is found.
8495

8596
###### Parameters
8697

87-
* `start` (`Heading`) — Start of range
88-
* `nodes` (`Array.<Node>`) — Nodes between `start` and `end`
89-
* `end` (`Heading?`) — End of range, if any
98+
* `start` ([`Heading`][heading]) — Start of range
99+
* `nodes` ([`Array.<Node>`][node]) — Nodes between `start` and `end`
100+
* `end` ([`Node?`][node]) — End of range, if any
90101
* `scope` (`Object`):
91102

92-
* `parent` (`Node`) — Parent of the range
103+
* `parent` ([`Node`][node]) — Parent of the range
93104
* `start` (`number`) — Index of `start` in `parent`
94105
* `end` (`number?`) — Index of `end` in `parent`
95106

@@ -122,3 +133,7 @@ Callback invoked when a range is found.
122133
[node]: https://github.com/syntax-tree/unist#node
123134

124135
[onrun]: #function-onrunstart-nodes-end-scope
136+
137+
[heading]: https://github.com/syntax-tree/mdast#heading
138+
139+
[test]: #function-testvalue-node

test.js

Lines changed: 88 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ var remark = require('remark');
55
var heading = require('./');
66

77
test('mdast-util-heading-range()', function (t) {
8-
t.plan(43);
8+
t.plan(55);
99

1010
t.equal(typeof heading, 'function', 'should be a function');
1111

@@ -320,19 +320,102 @@ test('mdast-util-heading-range()', function (t) {
320320
''
321321
].join('\n'), 'should not insert an empty `end`');
322322
});
323+
324+
t.equal(
325+
process(t, [
326+
'# Fo',
327+
'',
328+
'## Foo',
329+
'',
330+
'Bar',
331+
'',
332+
'[one]: example.com',
333+
'',
334+
'[two]: example.com',
335+
'',
336+
'# Fo',
337+
''
338+
].join('\n'), {test: 'foo', ignoreFinalDefinitions: true}),
339+
[
340+
'# Fo',
341+
'',
342+
'## Foo',
343+
'',
344+
'[one]: example.com',
345+
'',
346+
'[two]: example.com',
347+
'',
348+
'# Fo',
349+
''
350+
].join('\n'),
351+
'ignoreFinalDefinitions: should exclude definitions with an end heading'
352+
);
353+
354+
t.equal(
355+
process(t, [
356+
'# Fo',
357+
'',
358+
'## Foo',
359+
'',
360+
'[one]: example.com',
361+
'',
362+
'[two]: example.com',
363+
'',
364+
'# Fo',
365+
''
366+
].join('\n'), {test: 'foo', ignoreFinalDefinitions: true}),
367+
[
368+
'# Fo',
369+
'',
370+
'## Foo',
371+
'',
372+
'[one]: example.com',
373+
'',
374+
'[two]: example.com',
375+
'',
376+
'# Fo',
377+
''
378+
].join('\n'),
379+
'ignoreFinalDefinitions: should exclude only definitions'
380+
);
381+
382+
t.equal(
383+
process(t, [
384+
'# Fo',
385+
'',
386+
'## Foo',
387+
'',
388+
'Bar',
389+
'',
390+
'[one]: example.com',
391+
'',
392+
'[two]: example.com',
393+
''
394+
].join('\n'), {test: 'foo', ignoreFinalDefinitions: true}),
395+
[
396+
'# Fo',
397+
'',
398+
'## Foo',
399+
'',
400+
'[one]: example.com',
401+
'',
402+
'[two]: example.com',
403+
''
404+
].join('\n'),
405+
'ignoreFinalDefinitions: should exclude definitions in the final section'
406+
);
323407
});
324408

325409
/* Shortcut to process. */
326-
function process(t, value, name) {
410+
function process(t, value, options) {
327411
return remark()
328412
.use(function () {
329413
return function (node) {
330-
heading(node, name, function (start, nodes, end, scope) {
414+
heading(node, options, function (start, nodes, end, scope) {
331415
t.equal(typeof scope.start, 'number');
332416
t.assert(typeof scope.end === 'number' || scope.end === null);
333417
t.equal(scope.parent.type, 'root');
334-
335-
return [start].concat(end ? [end] : []);
418+
return [start].concat([end]);
336419
});
337420
};
338421
})

0 commit comments

Comments
 (0)