From e99aaad9cf8ab3661e609cd2cf6ac7da57cb7eb5 Mon Sep 17 00:00:00 2001 From: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> Date: Sun, 17 Sep 2023 01:54:04 +0530 Subject: [PATCH] fix(build): handle importing code snippets not having an extension (#2978) --- .../node/markdown/plugins/snippet.test.ts | 44 +++++++++++++++- src/node/markdown/plugins/snippet.ts | 51 +++++++++++-------- 2 files changed, 74 insertions(+), 21 deletions(-) diff --git a/__tests__/unit/node/markdown/plugins/snippet.test.ts b/__tests__/unit/node/markdown/plugins/snippet.test.ts index ce50d42b..a79fcafb 100644 --- a/__tests__/unit/node/markdown/plugins/snippet.test.ts +++ b/__tests__/unit/node/markdown/plugins/snippet.test.ts @@ -1,4 +1,40 @@ -import { dedent } from 'node/markdown/plugins/snippet' +import { dedent, rawPathToToken } from 'node/markdown/plugins/snippet' + +const removeEmptyKeys = >(obj: T) => { + return Object.fromEntries( + Object.entries(obj).filter(([, value]) => value !== '') + ) as T +} + +/* prettier-ignore */ +const rawPathTokenMap: [string, Partial<{ filepath: string, extension: string, title: string, region: string, lines: string, lang: string }>][] = [ + ['/path/to/file.extension', { filepath: '/path/to/file.extension', extension: 'extension', title: 'file.extension' }], + ['./path/to/file.extension', { filepath: './path/to/file.extension', extension: 'extension', title: 'file.extension' }], + ['/path to/file.extension', { filepath: '/path to/file.extension', extension: 'extension', title: 'file.extension' }], + ['./path to/file.extension', { filepath: './path to/file.extension', extension: 'extension', title: 'file.extension' }], + ['/path.to/file.extension', { filepath: '/path.to/file.extension', extension: 'extension', title: 'file.extension' }], + ['./path.to/file.extension', { filepath: './path.to/file.extension', extension: 'extension', title: 'file.extension' }], + ['/path .to/file.extension', { filepath: '/path .to/file.extension', extension: 'extension', title: 'file.extension' }], + ['./path .to/file.extension', { filepath: './path .to/file.extension', extension: 'extension', title: 'file.extension' }], + ['/path/to/file', { filepath: '/path/to/file', title: 'file' }], + ['./path/to/file', { filepath: './path/to/file', title: 'file' }], + ['/path to/file', { filepath: '/path to/file', title: 'file' }], + ['./path to/file', { filepath: './path to/file', title: 'file' }], + ['/path.to/file', { filepath: '/path.to/file', title: 'file' }], + ['./path.to/file', { filepath: './path.to/file', title: 'file' }], + ['/path .to/file', { filepath: '/path .to/file', title: 'file' }], + ['./path .to/file', { filepath: './path .to/file', title: 'file' }], + ['/path/to/file.extension#region', { filepath: '/path/to/file.extension', extension: 'extension', title: 'file.extension', region: '#region' }], + ['./path/to/file.extension {c#}', { filepath: './path/to/file.extension', extension: 'extension', title: 'file.extension', lang: 'c#' }], + ['/path to/file.extension {1,2,4-6}', { filepath: '/path to/file.extension', extension: 'extension', title: 'file.extension', lines: '1,2,4-6' }], + ['/path to/file.extension {1,2,4-6 c#}', { filepath: '/path to/file.extension', extension: 'extension', title: 'file.extension', lines: '1,2,4-6', lang: 'c#' }], + ['/path.to/file.extension [title]', { filepath: '/path.to/file.extension', extension: 'extension', title: 'title' }], + ['./path.to/file.extension#region {c#}', { filepath: './path.to/file.extension', extension: 'extension', title: 'file.extension', region: '#region', lang: 'c#' }], + ['/path/to/file#region {1,2,4-6}', { filepath: '/path/to/file', title: 'file', region: '#region', lines: '1,2,4-6' }], + ['./path/to/file#region {1,2,4-6 c#}', { filepath: './path/to/file', title: 'file', region: '#region', lines: '1,2,4-6', lang: 'c#' }], + ['/path to/file {1,2,4-6 c#} [title]', { filepath: '/path to/file', title: 'title', lines: '1,2,4-6', lang: 'c#' }], + ['./path to/file#region {1,2,4-6 c#} [title]', { filepath: './path to/file', title: 'title', region: '#region', lines: '1,2,4-6', lang: 'c#' }], +] describe('node/markdown/plugins/snippet', () => { describe('dedent', () => { @@ -57,4 +93,10 @@ describe('node/markdown/plugins/snippet', () => { `) }) }) + + test('rawPathToToken', () => { + rawPathTokenMap.forEach(([rawPath, token]) => { + expect(removeEmptyKeys(rawPathToToken(rawPath))).toEqual(token) + }) + }) }) diff --git a/src/node/markdown/plugins/snippet.ts b/src/node/markdown/plugins/snippet.ts index 71dcf05b..091818be 100644 --- a/src/node/markdown/plugins/snippet.ts +++ b/src/node/markdown/plugins/snippet.ts @@ -4,6 +4,35 @@ import type { RuleBlock } from 'markdown-it/lib/parser_block' import path from 'path' import type { MarkdownEnv } from '../../shared' +/** + * raw path format: "/path/to/file.extension#region {meta} [title]" + * where #region, {meta} and [title] are optional + * meta can be like '1,2,4-6 lang', 'lang' or '1,2,4-6' + * lang can contain special characters like C++, C#, F#, etc. + * path can be relative to the current file or absolute + * file extension is optional + * path can contain spaces and dots + * + * captures: ['/path/to/file.extension', 'extension', '#region', '{meta}', '[title]'] + */ +export const rawPathRegexp = + /^(.+?(?:(?:\.([a-z0-9]+))?))(?:(#[\w-]+))?(?: ?(?:{(\d+(?:[,-]\d+)*)? ?(\S+)?}))? ?(?:\[(.+)\])?$/ + +export function rawPathToToken(rawPath: string) { + const [ + filepath = '', + extension = '', + region = '', + lines = '', + lang = '', + rawTitle = '' + ] = (rawPathRegexp.exec(rawPath) || []).slice(1) + + const title = rawTitle || filepath.split('/').pop() || '' + + return { filepath, extension, region, lines, lang, title } +} + export function dedent(text: string): string { const lines = text.split('\n') @@ -91,32 +120,14 @@ export const snippetPlugin = (md: MarkdownIt, srcDir: string) => { const start = pos + 3 const end = state.skipSpacesBack(max, pos) - /** - * raw path format: "/path/to/file.extension#region {meta}" - * where #region and {meta} are optional - * and meta can be like '1,2,4-6 lang', 'lang' or '1,2,4-6' - * - * captures: ['/path/to/file.extension', 'extension', '#region', '{meta}', '[title]'] - */ - const rawPathRegexp = - /^(.+(?:\.([a-z0-9]+)))(?:(#[\w-]+))?(?: ?(?:{(\d+(?:[,-]\d+)*)? ?(\S+)?}))? ?(?:\[(.+)\])?$/ - const rawPath = state.src .slice(start, end) .trim() .replace(/^@/, srcDir) .trim() - const [ - filepath = '', - extension = '', - region = '', - lines = '', - lang = '', - rawTitle = '' - ] = (rawPathRegexp.exec(rawPath) || []).slice(1) - - const title = rawTitle || filepath.split('/').pop() || '' + const { filepath, extension, region, lines, lang, title } = + rawPathToToken(rawPath) state.line = startLine + 1