-
Notifications
You must be signed in to change notification settings - Fork 69
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
620 additions
and
112 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'myst-spec-ext': patch | ||
--- | ||
|
||
Add `include` node, that implements the `literalinclude` directive |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'myst-directives': patch | ||
--- | ||
|
||
Remove the codeBlockDirective, this is now the same as the `codeDirective`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
'myst-directives': patch | ||
'myst-cli': patch | ||
--- | ||
|
||
Add `literalinclude` directive |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { describe, expect, test } from 'vitest'; | ||
import { filterIncludedContent } from './include'; | ||
import { VFile } from 'vfile'; | ||
|
||
describe('filterIncludedContent', () => { | ||
test.each([ | ||
[{ startAt: 'ok' }, 'ok\nreally\ncool', 2, 0], | ||
[{ startAt: 'ok', endBefore: 'cool' }, 'ok\nreally', 2, 0], | ||
[{ startAt: 'ok', endBefore: 'ok' }, 'ok\nreally\ncool', 2, 1], | ||
[{ startAt: 'ok', endAt: 'cool' }, 'ok\nreally\ncool', 2, 0], | ||
[{ startAfter: 'k', endBefore: 'cool' }, 'really', 3, 0], | ||
[{ endBefore: 'cool' }, 'some\nok\nreally', 1, 0], | ||
[{ startAt: 'really' }, 'really\ncool', 3, 0], | ||
[{ startAfter: 'really' }, 'cool', 4, 0], | ||
[{ lines: [1, 3] }, 'some\nreally', 1, 0], | ||
[{ lines: [[1, 3]] }, 'some\nok\nreally', 1, 0], | ||
[{ lines: [1, [3]] }, 'some\nreally\ncool', 1, 0], | ||
[{ lines: [2, 1, 2] }, 'ok\nsome\nok', 2, 0], | ||
[{ lines: [1, -1] }, 'some\ncool', 1, 0], | ||
[{ lines: [-1] }, 'cool', 4, 0], | ||
[{ lines: [1, [-1]] }, 'some\ncool', 1, 0], | ||
[{ lines: [1, [-2]] }, 'some\nreally\ncool', 1, 0], | ||
[{ lines: [1, [-2, -1]] }, 'some\nreally\ncool', 1, 0], | ||
[{ lines: [1, [-1, -2]] }, 'some', 1, 1], | ||
])('%s', (t, a, sln, w) => { | ||
const vfile = new VFile(); | ||
const { content, startingLineNumber } = filterIncludedContent( | ||
vfile, | ||
t as any, | ||
'some\nok\nreally\ncool', | ||
); | ||
expect(content).toEqual(a); | ||
expect(startingLineNumber).toEqual(sln); | ||
expect(vfile.messages.length).toBe(w); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,166 @@ | ||
import fs from 'node:fs'; | ||
import type { GenericNode, GenericParent } from 'myst-common'; | ||
import { fileError, fileWarn, type GenericNode, type GenericParent } from 'myst-common'; | ||
import type { Code, Container, Include } from 'myst-spec-ext'; | ||
import { parseMyst } from '../process/index.js'; | ||
import { selectAll } from 'unist-util-select'; | ||
import { join, dirname } from 'node:path'; | ||
import type { ISession } from '../session/types.js'; | ||
import type { Caption } from 'myst-spec'; | ||
import type { VFile } from 'vfile'; | ||
|
||
/** | ||
* This is the {include} directive, that loads from disk. | ||
* | ||
* RST documentation: | ||
* - https://docutils.sourceforge.io/docs/ref/rst/directives.html#including-an-external-document-fragment | ||
*/ | ||
export function includeFilesDirective(session: ISession, filename: string, mdast: GenericParent) { | ||
const includeNodes = selectAll('include', mdast) as GenericNode[]; | ||
export function includeFilesDirective( | ||
session: ISession, | ||
vfile: VFile, | ||
filename: string, | ||
mdast: GenericParent, | ||
) { | ||
const includeNodes = selectAll('include', mdast) as Include[]; | ||
const dir = dirname(filename); | ||
includeNodes.forEach((node) => { | ||
const file = join(dir, node.file); | ||
if (!fs.existsSync(file)) { | ||
session.log.error(`Include Directive: Could not find "${file}" in "${filename}"`); | ||
fileError(vfile, `Include Directive: Could not find "${file}" in "${filename}"`); | ||
return; | ||
} | ||
const content = fs.readFileSync(file).toString(); | ||
const children = parseMyst(session, content, filename).children as GenericNode[]; | ||
node.children = children; | ||
const rawContent = fs.readFileSync(file).toString(); | ||
const { content, startingLineNumber } = filterIncludedContent(vfile, node.filter, rawContent); | ||
let children: GenericNode[]; | ||
if (node.literal) { | ||
const code: Code = { | ||
type: 'code', | ||
value: content, | ||
}; | ||
if (node.startingLineNumber === 'match') { | ||
// Replace the starting line number if it should match | ||
node.startingLineNumber = startingLineNumber; | ||
} | ||
// Move the code attributes to the code block | ||
( | ||
[ | ||
'lang', | ||
'emphasizeLines', | ||
'showLineNumbers', | ||
'startingLineNumber', | ||
'label', | ||
'identifier', | ||
] as const | ||
).forEach((attr) => { | ||
if (!node[attr]) return; | ||
code[attr] = node[attr] as any; | ||
delete node[attr]; | ||
}); | ||
if (!node.caption) { | ||
children = [code]; | ||
} else { | ||
const caption: Caption = { | ||
type: 'caption', | ||
children: [ | ||
{ | ||
type: 'paragraph', | ||
children: node.caption as any[], | ||
}, | ||
], | ||
}; | ||
const container: Container = { | ||
type: 'container', | ||
kind: 'code' as any, | ||
// Move the label to the container | ||
label: code.label, | ||
identifier: code.identifier, | ||
children: [code as any, caption], | ||
}; | ||
delete code.label; | ||
delete code.identifier; | ||
children = [container]; | ||
} | ||
} else { | ||
children = parseMyst(session, content, filename).children; | ||
} | ||
node.children = children as any; | ||
}); | ||
} | ||
|
||
function index(n: number, total: number): [number, number] | null { | ||
if (n > 0) return [n - 1, n]; | ||
if (n < 0) return [total + n, total + n + 1]; | ||
return null; | ||
} | ||
|
||
export function filterIncludedContent( | ||
vfile: VFile, | ||
filter: Include['filter'], | ||
rawContent: string, | ||
): { content: string; startingLineNumber?: number } { | ||
if (!filter || Object.keys(filter).length === 0) { | ||
return { content: rawContent, startingLineNumber: undefined }; | ||
} | ||
const lines = rawContent.split('\n'); | ||
let startingLineNumber: number | undefined; | ||
if (filter.lines) { | ||
const filtered = filter.lines.map((f) => { | ||
if (typeof f === 'number') { | ||
const ind = index(f, lines.length); | ||
if (!ind) { | ||
fileWarn(vfile, 'Invalid line number "0", indexing starts at 1'); | ||
return []; | ||
} | ||
if (!startingLineNumber) startingLineNumber = ind[0] + 1; | ||
return lines.slice(...ind); | ||
} | ||
const ind0 = index(f[0], lines.length); | ||
const ind1 = index(f[1] ?? lines.length, lines.length); | ||
if (!ind0 || !ind1) { | ||
fileWarn(vfile, 'Invalid line number "0", indexing starts at 1'); | ||
return []; | ||
} | ||
if (!startingLineNumber) startingLineNumber = ind0[0] + 1; | ||
const slice = lines.slice(ind0[0], ind1[1]); | ||
if (slice.length === 0) { | ||
fileWarn(vfile, `Unexpected lines, from "${f[0]}" to "${f[1] ?? ''}"`); | ||
} | ||
return slice; | ||
}); | ||
return { content: filtered.flat().join('\n'), startingLineNumber }; | ||
} | ||
let startLine = | ||
filter.startAt || filter.startAfter | ||
? lines.findIndex( | ||
(line) => | ||
(filter.startAt && line.includes(filter.startAt)) || | ||
(filter.startAfter && line.includes(filter.startAfter)), | ||
) | ||
: 0; | ||
if (startLine === -1) { | ||
fileWarn( | ||
vfile, | ||
`Could not find starting line including "${filter.startAt || filter.startAfter}"`, | ||
); | ||
startLine = 0; | ||
} | ||
if (filter.startAfter) startLine += 1; | ||
let endLine = | ||
filter.endAt || filter.endBefore | ||
? lines | ||
.slice(startLine + 1) | ||
.findIndex( | ||
(line) => | ||
(filter.endAt && line.includes(filter.endAt)) || | ||
(filter.endBefore && line.includes(filter.endBefore)), | ||
) | ||
: lines.length; | ||
if (endLine === -1) { | ||
fileWarn(vfile, `Could not find ending line including "${filter.endAt || filter.endBefore}"`); | ||
endLine = lines.length; | ||
} else if (filter.endAt || filter.endBefore) { | ||
endLine += startLine; | ||
if (filter.endAt) endLine += 1; | ||
} | ||
startingLineNumber = startLine + 1; | ||
return { content: lines.slice(startLine, endLine + 1).join('\n'), startingLineNumber }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { describe, expect, test } from 'vitest'; | ||
import { getCodeBlockOptions } from './code.js'; | ||
import { VFile } from 'vfile'; | ||
|
||
describe('Code block options', () => { | ||
test('default options', () => { | ||
const vfile = new VFile(); | ||
const opts = getCodeBlockOptions({}, vfile); | ||
expect(opts).toEqual({}); | ||
expect(vfile.messages.length).toEqual(0); | ||
}); | ||
test('number-lines', () => { | ||
const vfile = new VFile(); | ||
const opts = getCodeBlockOptions({ 'number-lines': 1 }, vfile); | ||
expect(opts).toEqual({ showLineNumbers: true }); | ||
expect(vfile.messages.length).toEqual(0); | ||
}); | ||
test('number-lines: 2', () => { | ||
const vfile = new VFile(); | ||
const opts = getCodeBlockOptions({ 'number-lines': 2 }, vfile); | ||
expect(opts).toEqual({ showLineNumbers: true, startingLineNumber: 2 }); | ||
expect(vfile.messages.length).toEqual(0); | ||
}); | ||
test('number-lines clashes with lineno-start', () => { | ||
const vfile = new VFile(); | ||
const opts = getCodeBlockOptions({ 'number-lines': 1, 'lineno-start': 2 }, vfile); | ||
expect(opts).toEqual({ showLineNumbers: true, startingLineNumber: 2 }); | ||
// Show warning! | ||
expect(vfile.messages.length).toEqual(1); | ||
}); | ||
test('lineno-start activates showLineNumbers', () => { | ||
const vfile = new VFile(); | ||
const opts = getCodeBlockOptions({ 'lineno-start': 1 }, vfile); | ||
expect(opts).toEqual({ showLineNumbers: true }); | ||
expect(vfile.messages.length).toEqual(0); | ||
}); | ||
test('emphasize-lines', () => { | ||
const vfile = new VFile(); | ||
const opts = getCodeBlockOptions({ 'emphasize-lines': '3,5' }, vfile); | ||
expect(opts).toEqual({ emphasizeLines: [3, 5] }); | ||
expect(vfile.messages.length).toEqual(0); | ||
}); | ||
// See https://github.com/executablebooks/jupyterlab-myst/issues/174 | ||
test(':lineno-start: 10, :emphasize-lines: 12,13', () => { | ||
const vfile = new VFile(); | ||
const opts = getCodeBlockOptions({ 'lineno-start': 10, 'emphasize-lines': '12,13' }, vfile); | ||
expect(opts).toEqual({ | ||
showLineNumbers: true, | ||
emphasizeLines: [12, 13], | ||
startingLineNumber: 10, | ||
}); | ||
expect(vfile.messages.length).toEqual(0); | ||
}); | ||
}); |
Oops, something went wrong.