diff --git a/packages/@vuepress/markdown/__tests__/__snapshots__/snippet.spec.js.snap b/packages/@vuepress/markdown/__tests__/__snapshots__/snippet.spec.js.snap index 3c97bcddcc..53893eef97 100644 --- a/packages/@vuepress/markdown/__tests__/__snapshots__/snippet.spec.js.snap +++ b/packages/@vuepress/markdown/__tests__/__snapshots__/snippet.spec.js.snap @@ -7,6 +7,15 @@ exports[`snippet import snippet 1`] = ` `; +exports[`snippet import snippet with comment/block transclusion => ::: 1`] = ` +
export default {
+ mounted() {
+ alert("yay!");
+ }
+};
+
+`;
+
exports[`snippet import snippet with highlight multiple lines 1`] = `
def snippet
+ puts 'hello'
+ puts 'from'
+ puts 'vue'
+end
+
+`;
+
+exports[`snippet import snippet with line transclusion 1`] = `
+<style lang="scss" scoped>
+.component {
+ display: flex;
+}
+</style>
+
+`;
+
+exports[`snippet import snippet with tag transclusion => style 1`] = `
+<style lang="scss" scoped>
+.component {
+ display: flex;
+}
+</style>
+
+`;
diff --git a/packages/@vuepress/markdown/__tests__/fragments/code-snippet-highlightLines-multiple.md b/packages/@vuepress/markdown/__tests__/fragments/code-snippet-highlightLines-multiple.md
index c06bf83f85..8b858770f2 100644
--- a/packages/@vuepress/markdown/__tests__/fragments/code-snippet-highlightLines-multiple.md
+++ b/packages/@vuepress/markdown/__tests__/fragments/code-snippet-highlightLines-multiple.md
@@ -1 +1 @@
-<<< @/packages/@vuepress/core/__test__/markdown/fragments/snippet.js{1-3}
+@[code highlight={1-3}](@/packages/@vuepress/markdown/__tests__/fragments/snippet.js)
diff --git a/packages/@vuepress/markdown/__tests__/fragments/code-snippet-highlightLines-single.md b/packages/@vuepress/markdown/__tests__/fragments/code-snippet-highlightLines-single.md
index d98c6ce859..31c5672695 100644
--- a/packages/@vuepress/markdown/__tests__/fragments/code-snippet-highlightLines-single.md
+++ b/packages/@vuepress/markdown/__tests__/fragments/code-snippet-highlightLines-single.md
@@ -1 +1 @@
-<<< @/packages/@vuepress/core/__test__/markdown/fragments/snippet.js{1,3}
+@[code highlight={1,3}](@/packages/@vuepress/markdown/__tests__/fragments/snippet.js)
diff --git a/packages/@vuepress/markdown/__tests__/fragments/code-snippet-transclude-line.md b/packages/@vuepress/markdown/__tests__/fragments/code-snippet-transclude-line.md
new file mode 100644
index 0000000000..81d66b7a24
--- /dev/null
+++ b/packages/@vuepress/markdown/__tests__/fragments/code-snippet-transclude-line.md
@@ -0,0 +1 @@
+@[code transclude={15-19}](@/packages/@vuepress/markdown/__tests__/fragments/snippet.vue)
diff --git a/packages/@vuepress/markdown/__tests__/fragments/code-snippet-transclude-tag.md b/packages/@vuepress/markdown/__tests__/fragments/code-snippet-transclude-tag.md
new file mode 100644
index 0000000000..31efeeaa2a
--- /dev/null
+++ b/packages/@vuepress/markdown/__tests__/fragments/code-snippet-transclude-tag.md
@@ -0,0 +1 @@
+@[code transcludeTag=style](@/packages/@vuepress/markdown/__tests__/fragments/snippet.vue)
diff --git a/packages/@vuepress/markdown/__tests__/fragments/code-snippet-transclude-with.md b/packages/@vuepress/markdown/__tests__/fragments/code-snippet-transclude-with.md
new file mode 100644
index 0000000000..1a8eac99f0
--- /dev/null
+++ b/packages/@vuepress/markdown/__tests__/fragments/code-snippet-transclude-with.md
@@ -0,0 +1 @@
+@[code transcludeWith=:::](@/packages/@vuepress/markdown/__tests__/fragments/snippet.vue)
diff --git a/packages/@vuepress/markdown/__tests__/fragments/code-snippet-with-lang.md b/packages/@vuepress/markdown/__tests__/fragments/code-snippet-with-lang.md
new file mode 100644
index 0000000000..ac11fc8659
--- /dev/null
+++ b/packages/@vuepress/markdown/__tests__/fragments/code-snippet-with-lang.md
@@ -0,0 +1 @@
+@[code lang=ruby](@/packages/@vuepress/markdown/__tests__/fragments/snippet.rb)
diff --git a/packages/@vuepress/markdown/__tests__/fragments/code-snippet.md b/packages/@vuepress/markdown/__tests__/fragments/code-snippet.md
index 5b87126e7f..1303113482 100644
--- a/packages/@vuepress/markdown/__tests__/fragments/code-snippet.md
+++ b/packages/@vuepress/markdown/__tests__/fragments/code-snippet.md
@@ -1 +1 @@
-<<< @/packages/@vuepress/core/__test__/markdown/fragments/snippet.js
\ No newline at end of file
+@[code](@/packages/@vuepress/markdown/__tests__/fragments/snippet.js)
diff --git a/packages/@vuepress/markdown/__tests__/fragments/snippet.rb b/packages/@vuepress/markdown/__tests__/fragments/snippet.rb
new file mode 100644
index 0000000000..bd8ad3cee6
--- /dev/null
+++ b/packages/@vuepress/markdown/__tests__/fragments/snippet.rb
@@ -0,0 +1,5 @@
+def snippet
+ puts 'hello'
+ puts 'from'
+ puts 'vue'
+end
diff --git a/packages/@vuepress/markdown/__tests__/fragments/snippet.vue b/packages/@vuepress/markdown/__tests__/fragments/snippet.vue
new file mode 100644
index 0000000000..7b4d097d7d
--- /dev/null
+++ b/packages/@vuepress/markdown/__tests__/fragments/snippet.vue
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/packages/@vuepress/markdown/__tests__/snippet.spec.js b/packages/@vuepress/markdown/__tests__/snippet.spec.js
index 5e05b481ed..2cdb667f92 100644
--- a/packages/@vuepress/markdown/__tests__/snippet.spec.js
+++ b/packages/@vuepress/markdown/__tests__/snippet.spec.js
@@ -12,15 +12,48 @@ describe('snippet', () => {
expect(output).toMatchSnapshot()
})
+ test('import snippet with lang option', async () => {
+ const input = await getFragment('code-snippet-with-lang')
+ const output = md.render(input)
+ expect(output).toMatchSnapshot()
+ expect(output).toMatch(/language-ruby/)
+ })
+
+ test('import snippet with line transclusion', async () => {
+ const input = await getFragment('code-snippet-transclude-line')
+ const output = md.render(input)
+ expect(output).toMatchSnapshot()
+ expect(output).not.toMatch(/template|script/)
+ expect(output).toMatch(/style/)
+ })
+
+ test('import snippet with comment/block transclusion => :::', async () => {
+ const input = await getFragment('code-snippet-transclude-with')
+ const output = md.render(input)
+ expect(output).toMatchSnapshot()
+ expect(output).not.toMatch(/template|script|style/)
+ expect(output).toMatch(/export default/)
+ })
+
+ test('import snippet with tag transclusion => style', async () => {
+ const input = await getFragment('code-snippet-transclude-tag')
+ const output = md.render(input)
+ expect(output).toMatchSnapshot()
+ expect(output).not.toMatch(/template|script/)
+ expect(output).toMatch(/style/)
+ })
+
test('import snippet with highlight single line', async () => {
const input = await getFragment('code-snippet-highlightLines-single')
const output = mdH.render(input)
expect(output).toMatchSnapshot()
+ expect(output).toMatch(/highlighted/)
})
test('import snippet with highlight multiple lines', async () => {
const input = await getFragment('code-snippet-highlightLines-multiple')
const output = mdH.render(input)
expect(output).toMatchSnapshot()
+ expect(output).toMatch(/highlighted/)
})
})
diff --git a/packages/@vuepress/markdown/lib/snippet.js b/packages/@vuepress/markdown/lib/snippet.js
index 895e7b2d5f..f4d37207b0 100644
--- a/packages/@vuepress/markdown/lib/snippet.js
+++ b/packages/@vuepress/markdown/lib/snippet.js
@@ -1,41 +1,148 @@
const { fs } = require('@vuepress/shared-utils')
-module.exports = function snippet (md, options = {}) {
- const root = options.root || process.cwd()
+const TRANSCLUDE_WITH = 'TRANSCLUDE_WITH'
+const TRANSCLUDE_LINE = 'TRANSCLUDE_LINE'
+const TRANSCLUDE_TAG = 'TRANSCLUDE_TAG'
+
+module.exports = function (md, options) {
+ const _root = options && options.root ? options.root : process.cwd()
+
+ const fileExists = f => {
+ return fs.existsSync(f)
+ }
+
+ const readFileSync = f => {
+ return fileExists(f) ? fs.readFileSync(f).toString() : `Not Found: ${f}`
+ }
+
+ const parseOptions = opts => {
+ const _t = {}
+ opts.trim().split(' ').forEach(pair => {
+ const [opt, value] = pair.split('=')
+ _t[opt] = value
+ })
+ return _t
+ }
+
+ const dataFactory = (state, pos, max) => {
+ const start = pos + 6
+ const end = state.skipSpacesBack(max, pos) - 1
+ const [opts, fullpathWithAtSym] = state.src.slice(start, end).trim().split('](')
+ const fullpath = fullpathWithAtSym.replace(/^@/, _root).trim()
+ const pathParts = fullpath.split('/')
+ const fileParts = pathParts[pathParts.length - 1].split('.')
+
+ return {
+ file: {
+ resolve: fullpath,
+ path: pathParts.slice(0, pathParts.length - 1).join('/'),
+ name: fileParts.slice(0, fileParts.length - 1).join('.'),
+ ext: fileParts[fileParts.length - 1]
+ },
+ options: parseOptions(opts),
+ content: readFileSync(fullpath),
+ fileExists: fileExists(fullpath)
+ }
+ }
+
+ const optionsMap = ({
+ options
+ }) => ({
+ hasHighlight: options.highlight || false,
+ hasTransclusion: options.transclude || options.transcludeWith || options.transcludeTag || false,
+ get transclusionType () {
+ if (options.transcludeWith) return TRANSCLUDE_WITH
+ if (options.transcludeTag) return TRANSCLUDE_TAG
+ if (options.transclude) return TRANSCLUDE_LINE
+ },
+ get meta () {
+ return this.hasHighlight ? options.highlight : ''
+ }
+ })
+
+ const contentTransclusion = ({
+ content,
+ options
+ }, transcludeType) => {
+ const lines = content.split('\n')
+ let _content = ''
+
+ if (transcludeType === TRANSCLUDE_LINE) {
+ const [tStart, tEnd] = options.transclude.replace(/[^\d|-]/g, '').split('-')
+
+ lines.forEach((line, idx) => {
+ const i = idx + 1
+ if (i >= tStart && i <= tEnd) {
+ _content += line + '\n'
+ }
+ })
+ } else if (transcludeType === TRANSCLUDE_TAG) {
+ const t = options.transcludeTag
+ const tag = new RegExp(`${t}>$|^<${t}`)
+ let matched = false
+
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i]
+
+ if (matched && tag.test(line)) {
+ _content += line + '\n'
+ break
+ } else if (matched) {
+ _content += line + '\n'
+ } else if (tag.test(line)) {
+ _content += line + '\n'
+ matched = true
+ }
+ }
+ } else if (transcludeType === TRANSCLUDE_WITH) {
+ const t = options.transcludeWith
+ const tag = new RegExp(t)
+ let matched = false
+
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i]
+
+ if (tag.test(line)) {
+ matched = !matched
+ continue
+ }
+
+ if (matched) {
+ _content += line + '\n'
+ }
+ }
+ }
+
+ return _content === '' ? 'No lines matched.' : _content
+ }
+
function parser (state, startLine, endLine, silent) {
- const CH = '<'.charCodeAt(0)
+ const matcher = [64, 91, 99, 111, 100, 101]
const pos = state.bMarks[startLine] + state.tShift[startLine]
const max = state.eMarks[startLine]
- // if it's indented more than 3 spaces, it should be a code block
if (state.sCount[startLine] - state.blkIndent >= 4) {
return false
}
- for (let i = 0; i < 3; ++i) {
+ for (let i = 0; i < 6; ++i) {
const ch = state.src.charCodeAt(pos + i)
- if (ch !== CH || pos + i >= max) return false
+ if (ch !== matcher[i] || pos + i >= max) return false
}
- if (silent) {
- return true
- }
+ if (silent) return true
- const start = pos + 3
- const end = state.skipSpacesBack(max, pos)
- const rawPath = state.src.slice(start, end).trim().replace(/^@/, root)
- const filename = rawPath.split(/[{\s]/).shift()
- const content = fs.existsSync(filename) ? fs.readFileSync(filename).toString() : 'Not found: ' + filename
- const meta = rawPath.replace(filename, '')
-
- state.line = startLine + 1
+ // handle code snippet include
+ const d = dataFactory(state, pos, max)
+ const opts = optionsMap(d)
const token = state.push('fence', 'code', 0)
- token.info = filename.split('.').pop() + meta
- token.content = content
+ token.info = (d.options.lang || d.file.ext) + opts.meta
+ token.content = d.fileExists && opts.hasTransclusion ? contentTransclusion(d, opts.transclusionType) : d.content
token.markup = '```'
token.map = [startLine, startLine + 1]
+ state.line = startLine + 1
return true
}