Skip to content

Commit 109b905

Browse files
danpoqgatsbybot
andauthored
feat(remark-embed-snippet): embed specific lines (#21907)
* feat(remark-embed-snippet): embed specific lines * refactor(remark-embed-snippet): util file names * fix(remark-embed-snippet): language of snippet * feat(remark-embed-snippet): parse numeric range Co-authored-by: gatsbybot <mathews.kyle+gatsbybot@gmail.com>
1 parent 0b0f5f3 commit 109b905

File tree

3 files changed

+117
-6
lines changed

3 files changed

+117
-6
lines changed

packages/gatsby-remark-embed-snippet/README.md

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,8 @@ The resulting HTML generated from the markdown file above would look something l
185185

186186
### Highlighting Lines
187187

188-
You can also specify specific lines for Prism to highlight using
189-
`highlight-line` and `highlight-next-line` comments. You can also specify a
190-
range of lines to highlight, relative to a `highlight-range` comment.
188+
You can specify specific lines for Prism to highlight using
189+
`highlight-line` and `highlight-next-line` comments. You can also specify a range of lines to highlight, relative to a `highlight-range` comment.
191190

192191
**JavaScript example**:
193192

@@ -250,8 +249,49 @@ quz: "highlighted"
250249
251250
It's also possible to specify a range of lines to be hidden.
252251
252+
You can either specify line ranges in the embed using the syntax:
253+
254+
- #Lx - Embed one line from a file
255+
- #Lx-y - Embed a range of lines from a file
256+
- #Lx-y,a-b - Embed non-consecutive ranges of lines from a file
257+
258+
**Markdown example**:
259+
260+
```markdown
261+
This is the JSX of my app:
262+
263+
`embed:App.js#L6-8`
264+
```
265+
266+
With this example snippet:
267+
268+
```js
269+
import React from "react"
270+
import ReactDOM from "react-dom"
271+
272+
function App() {
273+
return (
274+
<div className="App">
275+
<h1>Hello world</h1>
276+
</div>
277+
)
278+
}
279+
```
280+
281+
Will produce something like this:
282+
283+
```markdown
284+
This is the JSX of my app:
285+
286+
<div className="App">
287+
<h1>Hello world</h1>
288+
</div>
289+
```
290+
253291
**JavaScript example**:
254292

293+
You can also add `// hide-range` comments to your files.
294+
255295
```jsx
256296
// hide-range{1-2}
257297
import React from "react"

packages/gatsby-remark-embed-snippet/src/__tests__/index.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,56 @@ describe(`gatsby-remark-embed-snippet`, () => {
3636
)
3737
})
3838

39+
it(`should display a code block of a single line`, () => {
40+
const codeBlockValue = ` console.log('hello world')`
41+
fs.readFileSync.mockReturnValue(`function test() {
42+
${codeBlockValue}
43+
}`)
44+
45+
const markdownAST = remark.parse(`\`embed:hello-world.js#L2\``)
46+
const transformed = plugin({ markdownAST }, { directory: `examples` })
47+
48+
const codeBlock = transformed.children[0].children[0]
49+
50+
expect(codeBlock.value).toEqual(codeBlockValue)
51+
})
52+
53+
it(`should display a code block of a range of lines`, () => {
54+
const codeBlockValue = ` if (window.location.search.indexOf('query') > -1) {
55+
console.log('The user is searching')
56+
}`
57+
fs.readFileSync.mockReturnValue(`function test() {
58+
${codeBlockValue}
59+
}`)
60+
61+
const markdownAST = remark.parse(`\`embed:hello-world.js#L2-4\``)
62+
const transformed = plugin({ markdownAST }, { directory: `examples` })
63+
64+
const codeBlock = transformed.children[0].children[0]
65+
66+
expect(codeBlock.value).toEqual(codeBlockValue)
67+
})
68+
69+
it(`should display a code block of a range of non-consecutive lines`, () => {
70+
const notInSnippet = `lineShouldNotBeInSnippet();`
71+
fs.readFileSync.mockReturnValue(`function test() {
72+
if (window.location.search.indexOf('query') > -1) {
73+
console.log('The user is searching')
74+
}
75+
}
76+
${notInSnippet}
77+
window.addEventListener('resize', () => {
78+
test();
79+
})`)
80+
81+
const markdownAST = remark.parse(`\`embed:hello-world.js#L2-4,7-9\``)
82+
const transformed = plugin({ markdownAST }, { directory: `examples` })
83+
84+
const codeBlock = transformed.children[0].children[0]
85+
86+
expect(codeBlock.value).not.toContain(notInSnippet)
87+
})
88+
3989
it(`should error if an invalid file path is specified`, () => {
4090
fs.existsSync.mockImplementation(path => path !== `examples/hello-world.js`)
4191

packages/gatsby-remark-embed-snippet/src/index.js

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const path = require(`path`)
44
const fs = require(`fs`)
55
const normalizePath = require(`normalize-path`)
66
const visit = require(`unist-util-visit`)
7+
const rangeParser = require(`parse-numeric-range`)
78

89
// Language defaults to extension.toLowerCase();
910
// This map tracks languages that don't match their extension.
@@ -46,21 +47,41 @@ module.exports = ({ markdownAST, markdownNode }, { directory } = {}) => {
4647

4748
if (value.startsWith(`embed:`)) {
4849
const file = value.substr(6)
49-
const snippetPath = normalizePath(path.join(directory, file))
50+
let snippetPath = normalizePath(path.join(directory, file))
51+
52+
// Embed specific lines numbers of a file
53+
let lines = []
54+
const rangePrefixIndex = snippetPath.indexOf(`#L`)
55+
if (rangePrefixIndex > -1) {
56+
const range = snippetPath.slice(rangePrefixIndex + 2)
57+
if (range.length === 1) {
58+
lines = [Number.parseInt(range, 10)]
59+
} else {
60+
lines = rangeParser.parse(range)
61+
}
62+
// Remove everything after the range prefix from file path
63+
snippetPath = snippetPath.slice(0, rangePrefixIndex)
64+
}
5065

5166
if (!fs.existsSync(snippetPath)) {
5267
throw Error(`Invalid snippet specified; no such file "${snippetPath}"`)
5368
}
5469

55-
const code = fs.readFileSync(snippetPath, `utf8`).trim()
70+
let code = fs.readFileSync(snippetPath, `utf8`).trim()
71+
if (lines.length) {
72+
code = code
73+
.split(`\n`)
74+
.filter((_, lineNumber) => lines.includes(lineNumber + 1))
75+
.join(`\n`)
76+
}
5677

5778
// PrismJS's theme styles are targeting pre[class*="language-"]
5879
// to apply its styles. We do the same here so that users
5980
// can apply a PrismJS theme and get the expected, ready-to-use
6081
// outcome without any additional CSS.
6182
//
6283
// @see https://github.com/PrismJS/prism/blob/1d5047df37aacc900f8270b1c6215028f6988eb1/themes/prism.css#L49-L54
63-
const language = getLanguage(file)
84+
const language = getLanguage(snippetPath)
6485

6586
// Change the node type to code, insert our file as value and set language.
6687
node.type = `code`

0 commit comments

Comments
 (0)