diff --git a/__tests__/unit/node/markdown/plugins/snippet.test.ts b/__tests__/unit/node/markdown/plugins/snippet.test.ts
index aa940784..15f72fc5 100644
--- a/__tests__/unit/node/markdown/plugins/snippet.test.ts
+++ b/__tests__/unit/node/markdown/plugins/snippet.test.ts
@@ -1,7 +1,8 @@
import {
dedent,
- findRegion,
- rawPathToToken
+ findRegions,
+ rawPathToToken,
+ stripMarkers
} from 'node/markdown/plugins/snippet'
import { expect } from 'vitest'
@@ -39,6 +40,7 @@ const rawPathTokenMap: [string, Partial<{ filepath: string, extension: string, t
['./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#' }],
+ ['./path/to/file {C++}', { filepath: './path/to/file', title: 'file', lang: 'C++' }],
]
describe('node/markdown/plugins/snippet', () => {
@@ -97,6 +99,10 @@ describe('node/markdown/plugins/snippet', () => {
};"
`)
})
+
+ test('empty string remains empty', () => {
+ expect(dedent('')).toBe('')
+ })
})
describe('rawPathToToken', () => {
@@ -106,9 +112,14 @@ describe('node/markdown/plugins/snippet', () => {
})
describe('findRegion', () => {
- it('returns null when no region markers are present', () => {
- const lines = ['function foo() {', ' console.log("hello");', '}']
- expect(findRegion(lines, 'foo')).toBeNull()
+ it('returns empty array when no region markers are present', () => {
+ const lines = [
+ 'function foo() {',
+ ' console.log("hello");',
+ ' return "foo";',
+ '}'
+ ]
+ expect(findRegions(lines, 'foo')).toHaveLength(0)
})
it('ignores non-matching region names', () => {
@@ -117,24 +128,24 @@ describe('node/markdown/plugins/snippet', () => {
'some code here',
'// #endregion regionA'
]
- expect(findRegion(lines, 'regionC')).toBeNull()
+ expect(findRegions(lines, 'regionC')).toHaveLength(0)
})
- it('returns null if a region start marker exists without a matching end marker', () => {
+ it('returns empty array if a region start marker exists without a matching end marker', () => {
const lines = [
'// #region missingEnd',
'console.log("inside region");',
'console.log("still inside");'
]
- expect(findRegion(lines, 'missingEnd')).toBeNull()
+ expect(findRegions(lines, 'missingEnd')).toHaveLength(0)
})
- it('returns null if an end marker exists without a preceding start marker', () => {
+ it('returns empty array if an end marker exists without a preceding start marker', () => {
const lines = [
'// #endregion ghostRegion',
'console.log("stray end marker");'
]
- expect(findRegion(lines, 'ghostRegion')).toBeNull()
+ expect(findRegions(lines, 'ghostRegion')).toHaveLength(0)
})
it('detects C#/JavaScript style region markers with matching tags', () => {
@@ -145,12 +156,18 @@ describe('node/markdown/plugins/snippet', () => {
'#endregion hello',
'Console.WriteLine("After region");'
]
- const result = findRegion(lines, 'hello')
- expect(result).not.toBeNull()
+ const result = findRegions(lines, 'hello')
+ expect(result).toHaveLength(1)
if (result) {
- expect(lines.slice(result.start, result.end).join('\n')).toBe(
- 'Console.WriteLine("Hello, World!");'
- )
+ expect(
+ result
+ .flatMap((r) =>
+ lines
+ .slice(r.start, r.end)
+ .filter((l) => !(r.re.start.test(l) || r.re.end.test(l)))
+ )
+ .join('\n')
+ ).toBe('Console.WriteLine("Hello, World!");')
}
})
@@ -162,12 +179,18 @@ describe('node/markdown/plugins/snippet', () => {
'#endregion',
'Console.WriteLine("After region");'
]
- const result = findRegion(lines, 'hello')
- expect(result).not.toBeNull()
+ const result = findRegions(lines, 'hello')
+ expect(result).toHaveLength(1)
if (result) {
- expect(lines.slice(result.start, result.end).join('\n')).toBe(
- 'Console.WriteLine("Hello, World!");'
- )
+ expect(
+ result
+ .flatMap((r) =>
+ lines
+ .slice(r.start, r.end)
+ .filter((l) => !(r.re.start.test(l) || r.re.end.test(l)))
+ )
+ .join('\n')
+ ).toBe('Console.WriteLine("Hello, World!");')
}
})
@@ -179,124 +202,182 @@ describe('node/markdown/plugins/snippet', () => {
' #endregion hello',
' Console.WriteLine("After region");'
]
- const result = findRegion(lines, 'hello')
- expect(result).not.toBeNull()
+ const result = findRegions(lines, 'hello')
+ expect(result).toHaveLength(1)
if (result) {
- expect(lines.slice(result.start, result.end).join('\n')).toBe(
- ' Console.WriteLine("Hello, World!");'
- )
+ expect(
+ result
+ .flatMap((r) =>
+ lines
+ .slice(r.start, r.end)
+ .filter((l) => !(r.re.start.test(l) || r.re.end.test(l)))
+ )
+ .join('\n')
+ ).toBe(' Console.WriteLine("Hello, World!");')
}
})
it('detects TypeScript style region markers', () => {
const lines = [
'let regexp: RegExp[] = [];',
- '// #region foo',
+ '// #region hello',
'let start = -1;',
- '// #endregion foo'
+ '// #endregion hello'
]
- const result = findRegion(lines, 'foo')
- expect(result).not.toBeNull()
+ const result = findRegions(lines, 'hello')
+ expect(result).toHaveLength(1)
if (result) {
- expect(lines.slice(result.start, result.end).join('\n')).toBe(
- 'let start = -1;'
- )
+ expect(
+ result
+ .flatMap((r) =>
+ lines
+ .slice(r.start, r.end)
+ .filter((l) => !(r.re.start.test(l) || r.re.end.test(l)))
+ )
+ .join('\n')
+ ).toBe('let start = -1;')
}
})
it('detects CSS style region markers', () => {
const lines = [
'.body-content {',
- '/* #region foo */',
+ '/* #region hello */',
' padding-left: 15px;',
- '/* #endregion foo */',
+ '/* #endregion hello */',
' padding-right: 15px;',
'}'
]
- const result = findRegion(lines, 'foo')
- expect(result).not.toBeNull()
+ const result = findRegions(lines, 'hello')
+ expect(result).toHaveLength(1)
if (result) {
- expect(lines.slice(result.start, result.end).join('\n')).toBe(
- ' padding-left: 15px;'
- )
+ expect(
+ result
+ .flatMap((r) =>
+ lines
+ .slice(r.start, r.end)
+ .filter((l) => !(r.re.start.test(l) || r.re.end.test(l)))
+ )
+ .join('\n')
+ ).toBe(' padding-left: 15px;')
}
})
it('detects HTML style region markers', () => {
const lines = [
'
Some content
',
- '',
+ '',
' Hello world
',
- '',
+ '',
'Other content
'
]
- const result = findRegion(lines, 'foo')
- expect(result).not.toBeNull()
+ const result = findRegions(lines, 'hello')
+ expect(result).toHaveLength(1)
if (result) {
- expect(lines.slice(result.start, result.end).join('\n')).toBe(
- ' Hello world
'
- )
+ expect(
+ result
+ .flatMap((r) =>
+ lines
+ .slice(r.start, r.end)
+ .filter((l) => !(r.re.start.test(l) || r.re.end.test(l)))
+ )
+ .join('\n')
+ ).toBe(' Hello world
')
}
})
it('detects Visual Basic style region markers (with case-insensitive "End")', () => {
const lines = [
'Console.WriteLine("VB")',
- '#Region VBRegion',
+ '#Region hello',
' Console.WriteLine("Inside region")',
- '#End Region VBRegion',
+ '#End Region hello',
'Console.WriteLine("Done")'
]
- const result = findRegion(lines, 'VBRegion')
- expect(result).not.toBeNull()
+ const result = findRegions(lines, 'hello')
+ expect(result).toHaveLength(1)
if (result) {
- expect(lines.slice(result.start, result.end).join('\n')).toBe(
- ' Console.WriteLine("Inside region")'
- )
+ expect(
+ result
+ .flatMap((r) =>
+ lines
+ .slice(r.start, r.end)
+ .filter((l) => !(r.re.start.test(l) || r.re.end.test(l)))
+ )
+ .join('\n')
+ ).toBe(' Console.WriteLine("Inside region")')
}
})
it('detects Bat style region markers', () => {
- const lines = ['::#region foo', 'echo off', '::#endregion foo']
- const result = findRegion(lines, 'foo')
- expect(result).not.toBeNull()
+ const lines = ['::#region hello', '@ECHO OFF', 'REM #endregion hello']
+ const result = findRegions(lines, 'hello')
+ expect(result).toHaveLength(1)
if (result) {
- expect(lines.slice(result.start, result.end).join('\n')).toBe(
- 'echo off'
- )
+ expect(
+ result
+ .flatMap((r) =>
+ lines
+ .slice(r.start, r.end)
+ .filter((l) => !(r.re.start.test(l) || r.re.end.test(l)))
+ )
+ .join('\n')
+ ).toBe('@ECHO OFF')
}
})
it('detects C/C++ style region markers using #pragma', () => {
const lines = [
- '#pragma region foo',
+ '#pragma region hello',
'int a = 1;',
- '#pragma endregion foo'
+ '#pragma endregion hello'
]
- const result = findRegion(lines, 'foo')
- expect(result).not.toBeNull()
+ const result = findRegions(lines, 'hello')
+ expect(result).toHaveLength(1)
if (result) {
- expect(lines.slice(result.start, result.end).join('\n')).toBe(
- 'int a = 1;'
- )
+ expect(
+ result
+ .flatMap((r) =>
+ lines
+ .slice(r.start, r.end)
+ .filter((l) => !(r.re.start.test(l) || r.re.end.test(l)))
+ )
+ .join('\n')
+ ).toBe('int a = 1;')
}
})
- it('returns the first complete region when multiple regions exist', () => {
+ it('returns all regions with the same name when multiple exist', () => {
const lines = [
- '// #region foo',
+ '// #region hello',
'first region content',
- '// #endregion foo',
- '// #region foo',
+ '// #endregion hello',
+ 'between regions content',
+ '// #region hello',
'second region content',
- '// #endregion foo'
+ '// #endregion',
+ 'between regions content',
+ '// #region hello',
+ 'third region content',
+ '// #endregion hello',
+ 'below regions content'
]
- const result = findRegion(lines, 'foo')
- expect(result).not.toBeNull()
+ const result = findRegions(lines, 'hello')
+ expect(result).toHaveLength(3)
if (result) {
- expect(lines.slice(result.start, result.end).join('\n')).toBe(
- 'first region content'
- )
+ const extracted = result
+ .flatMap((r) =>
+ lines
+ .slice(r.start, r.end)
+ .filter((l) => !(r.re.start.test(l) || r.re.end.test(l)))
+ )
+ .join('\n')
+ const expected = [
+ 'first region content',
+ 'second region content',
+ 'third region content'
+ ].join('\n')
+ expect(extracted).toBe(expected)
}
})
@@ -309,18 +390,102 @@ describe('node/markdown/plugins/snippet', () => {
'// #endregion bar',
'// #endregion foo'
]
- const result = findRegion(lines, 'foo')
- expect(result).not.toBeNull()
+ const result = findRegions(lines, 'foo')
+ expect(result).toHaveLength(1)
if (result) {
- const extracted = lines.slice(result.start, result.end).join('\n')
+ const extracted = result
+ .flatMap((r) =>
+ lines
+ .slice(r.start, r.end)
+ .filter((l) => !(r.re.start.test(l) || r.re.end.test(l)))
+ )
+ .join('\n')
const expected = [
"console.log('line before nested');",
- '// #region bar',
- "console.log('nested content');",
- '// #endregion bar'
+ "console.log('nested content');"
].join('\n')
expect(extracted).toBe(expected)
}
})
+
+ it('handles region names with hyphens and special characters', () => {
+ const lines = [
+ '// #region complex-name_123',
+ 'const x = 1;',
+ '// #endregion complex-name_123'
+ ]
+ const result = findRegions(lines, 'complex-name_123')
+ expect(result).toHaveLength(1)
+ if (result) {
+ const extracted = result
+ .flatMap((r) =>
+ lines
+ .slice(r.start, r.end)
+ .filter((l) => !(r.re.start.test(l) || r.re.end.test(l)))
+ )
+ .join('\n')
+ expect(extracted).toBe('const x = 1;')
+ }
+ })
+ })
+
+ describe('stripRegionMarkers', () => {
+ it('removes #region and #endregion lines', () => {
+ const src = [
+ '// #region A',
+ '// #region B',
+ 'console.log("Hello, World!");',
+ '// #endregion B',
+ '// #endregion A'
+ ]
+ expect(stripMarkers(src, true)).toBe('console.log("Hello, World!");')
+ })
+
+ it('does not remove any marker if stripRegionMarkers is false', () => {
+ const src = [
+ '// #region A',
+ '// #region B',
+ 'console.log("Hello, World!");',
+ '// #endregion B',
+ '// #endregion A'
+ ]
+ expect(stripMarkers(src, false)).toBe(src.join('\n'))
+ })
+
+ it('removes region markers for various syntaxes', () => {
+ const src = [
+ '',
+ 'hi
',
+ '',
+ '/* #region css */',
+ 'body {}',
+ '/* #endregion css */',
+ '#pragma region cpp',
+ 'int main(){}',
+ '#pragma endregion cpp',
+ '::#region bat',
+ 'ECHO ON',
+ 'REM #endregion bat'
+ ]
+ const out = stripMarkers(src, true)
+ expect(out).not.toContain('#region')
+ expect(out).not.toContain('#endregion')
+ expect(out).toContain('hi
')
+ expect(out).toContain('body {}')
+ expect(out).toContain('int main(){}')
+ expect(out).toContain('ECHO ON')
+ })
+
+ it('removes markers even if indented or with extra spaces', () => {
+ const src = [
+ ' // #region spaced ',
+ '\t/* #region */',
+ 'code();',
+ ' // #endregion spaced',
+ '/* #endregion */'
+ ]
+ const out = stripMarkers(src, true)
+ expect(out.trim()).toBe('code();')
+ })
})
})
diff --git a/docs/en/guide/markdown.md b/docs/en/guide/markdown.md
index 7249fac2..47ed2b76 100644
--- a/docs/en/guide/markdown.md
+++ b/docs/en/guide/markdown.md
@@ -656,12 +656,12 @@ The value of `@` corresponds to the source root. By default it's the VitePress p
:::
-You can also use a [VS Code region](https://code.visualstudio.com/docs/editor/codebasics#_folding) to only include the corresponding part of the code file. You can provide a custom region name after a `#` following the filepath:
+You can also use a [VS Code region](https://code.visualstudio.com/docs/editor/codebasics#_folding) to only include the corresponding part of the code file. You can provide a custom region name after a `#` following the filepath, and all regions with that name will be imported:
**Input**
```md
-<<< @/snippets/snippet-with-region.js#snippet{1}
+<<< @/snippets/snippet-with-region.js#snippet{2,5}
```
**Code file**
@@ -670,7 +670,7 @@ You can also use a [VS Code region](https://code.visualstudio.com/docs/editor/co
**Output**
-<<< @/snippets/snippet-with-region.js#snippet{1}
+<<< @/snippets/snippet-with-region.js#snippet{2,5}
You can also specify the language inside the braces (`{}`) like this:
@@ -856,7 +856,7 @@ Can be created using `.foorc.json`.
The format of the selected line range can be: `{3,}`, `{,10}`, `{1,10}`
-You can also use a [VS Code region](https://code.visualstudio.com/docs/editor/codebasics#_folding) to only include the corresponding part of the code file. You can provide a custom region name after a `#` following the filepath:
+You can also use a [VS Code region](https://code.visualstudio.com/docs/editor/codebasics#_folding) to only include the corresponding part of the code file. You can provide a custom region name after a `#` following the filepath, and all regions with that name will be included:
**Input**
diff --git a/docs/snippets/snippet-with-region.js b/docs/snippets/snippet-with-region.js
index 9c7faaeb..fd087862 100644
--- a/docs/snippets/snippet-with-region.js
+++ b/docs/snippets/snippet-with-region.js
@@ -1,7 +1,15 @@
// #region snippet
function foo() {
- // ..
+ console.log('foo')
}
// #endregion snippet
-export default foo
+console.log('this line is not in #region snippet!')
+
+// #region snippet
+function bar() {
+ console.log('bar')
+}
+// #endregion snippet
+
+export { bar, foo }
diff --git a/src/node/markdown/markdown.ts b/src/node/markdown/markdown.ts
index fbe92b30..de817b7f 100644
--- a/src/node/markdown/markdown.ts
+++ b/src/node/markdown/markdown.ts
@@ -140,6 +140,11 @@ export interface MarkdownOptions extends MarkdownItAsyncOptions {
* @default 'Copy Code'
*/
codeCopyButtonTitle?: string
+ /**
+ * Remove all #region markers when including snippets
+ * @default false
+ */
+ stripMarkersFromSnippets?: boolean
/* ==================== Markdown It Plugins ==================== */
@@ -274,7 +279,7 @@ export async function createMarkdownRenderer(
codeCopyButtonTitle,
languageLabel: options.languageLabel
})
- snippetPlugin(md, srcDir)
+ snippetPlugin(md, srcDir, options.stripMarkersFromSnippets)
containerPlugin(md, options.container)
imagePlugin(md, options.image)
linkPlugin(
diff --git a/src/node/markdown/plugins/snippet.ts b/src/node/markdown/plugins/snippet.ts
index 71e5e76c..87ba8da1 100644
--- a/src/node/markdown/plugins/snippet.ts
+++ b/src/node/markdown/plugins/snippet.ts
@@ -86,40 +86,63 @@ const markers = [
}
]
-export function findRegion(lines: Array, regionName: string) {
- let chosen: { re: (typeof markers)[number]; start: number } | null = null
- // find the regex pair for a start marker that matches the given region name
- for (let i = 0; i < lines.length; i++) {
- for (const re of markers) {
- if (re.start.exec(lines[i])?.[1] === regionName) {
- chosen = { re, start: i + 1 }
- break
+export function findRegions(lines: string[], regionName: string) {
+ const returned: {
+ re: (typeof markers)[number]
+ start: number
+ end: number
+ }[] = []
+
+ for (const re of markers) {
+ let nestedCounter = 0
+ let start: number | null = null
+
+ for (let i = 0; i < lines.length; i++) {
+ // find region start
+ const startMatch = re.start.exec(lines[i])
+ if (startMatch?.[1] === regionName) {
+ if (nestedCounter === 0) start = i + 1
+ nestedCounter++
+ continue
+ }
+
+ if (nestedCounter === 0) continue
+
+ // find region end
+ const endMatch = re.end.exec(lines[i])
+ if (endMatch?.[1] === regionName || endMatch?.[1] === '') {
+ nestedCounter--
+ // if all nested regions ended
+ if (nestedCounter === 0 && start != null) {
+ returned.push({ re, start, end: i })
+ start = null
+ }
}
}
- if (chosen) break
- }
- if (!chosen) return null
-
- let counter = 1
- // scan the rest of the lines to find the matching end marker, handling nested markers
- for (let i = chosen.start; i < lines.length; i++) {
- // check for an inner start marker for the same region
- if (chosen.re.start.exec(lines[i])?.[1] === regionName) {
- counter++
- continue
- }
- // check for an end marker for the same region
- const endRegion = chosen.re.end.exec(lines[i])?.[1]
- // allow empty region name on the end marker as a fallback
- if (endRegion === regionName || endRegion === '') {
- if (--counter === 0) return { ...chosen, end: i }
- }
+
+ if (returned.length > 0) break
}
- return null
+ return returned
+}
+
+export function stripMarkers(lines: string[], stripMarkers: boolean): string {
+ if (!stripMarkers) return lines.join('\n')
+ return lines
+ .filter((l) => {
+ for (const m of markers) {
+ if (m.start.test(l) || m.end.test(l)) return false
+ }
+ return true
+ })
+ .join('\n')
}
-export const snippetPlugin = (md: MarkdownItAsync, srcDir: string) => {
+export const snippetPlugin = (
+ md: MarkdownItAsync,
+ srcDir: string,
+ stripMarkersFromSnippets = false
+) => {
const parser: RuleBlock = (state, startLine, endLine, silent) => {
const CH = '<'.charCodeAt(0)
const pos = state.bMarks[startLine] + state.tShift[startLine]
@@ -175,7 +198,7 @@ export const snippetPlugin = (md: MarkdownItAsync, srcDir: string) => {
const [tokens, idx, , { includes }] = args
const token = tokens[idx]
// @ts-ignore
- const [src, regionName] = token.src ?? []
+ const [src, region] = token.src ?? []
if (!src) return fence(...args)
@@ -183,29 +206,42 @@ export const snippetPlugin = (md: MarkdownItAsync, srcDir: string) => {
includes.push(src)
}
- const isAFile = fs.statSync(src).isFile()
- if (!fs.existsSync(src) || !isAFile) {
- token.content = isAFile
- ? `Code snippet path not found: ${src}`
- : `Invalid code snippet option`
+ if (!fs.existsSync(src)) {
+ token.content = `Code snippet path not found: ${src}`
+ token.info = ''
+ return fence(...args)
+ }
+
+ if (!fs.statSync(src).isFile()) {
+ token.content = `Invalid code snippet option`
token.info = ''
return fence(...args)
}
let content = fs.readFileSync(src, 'utf8').replace(/\r\n/g, '\n')
- if (regionName) {
+ if (region) {
const lines = content.split('\n')
- const region = findRegion(lines, regionName)
+ const regions = findRegions(lines, region)
- if (region) {
+ if (regions.length > 0) {
content = dedent(
- lines
- .slice(region.start, region.end)
- .filter((l) => !(region.re.start.test(l) || region.re.end.test(l)))
- .join('\n')
+ stripMarkers(
+ regions.flatMap((r) =>
+ lines
+ .slice(r.start, r.end)
+ .filter((l) => !(r.re.start.test(l) || r.re.end.test(l)))
+ ),
+ stripMarkersFromSnippets
+ )
)
+ } else {
+ token.content = `No region #${region} found in path: ${src}`
+ token.info = ''
+ return fence(...args)
}
+ } else {
+ content = stripMarkers(content.split('\n'), stripMarkersFromSnippets)
}
token.content = content
diff --git a/src/node/utils/processIncludes.ts b/src/node/utils/processIncludes.ts
index aa98ccbd..bf4bcf22 100644
--- a/src/node/utils/processIncludes.ts
+++ b/src/node/utils/processIncludes.ts
@@ -3,7 +3,7 @@ import matter from 'gray-matter'
import type { MarkdownItAsync } from 'markdown-it-async'
import path from 'node:path'
import c from 'picocolors'
-import { findRegion } from '../markdown/plugins/snippet'
+import { findRegions } from '../markdown/plugins/snippet'
import { slash, type MarkdownEnv } from '../shared'
export function processIncludes(
@@ -42,9 +42,9 @@ export function processIncludes(
if (region) {
const [regionName] = region
const lines = content.split(/\r?\n/)
- let { start, end } = findRegion(lines, regionName.slice(1)) ?? {}
+ let regions = findRegions(lines, regionName.slice(1))
- if (start === undefined) {
+ if (regions.length === 0) {
// region not found, it might be a header
const tokens = md
.parse(content, {
@@ -58,18 +58,26 @@ export function processIncludes(
)
const token = tokens[idx]
if (token) {
- start = token.map![1]
+ const start = token.map![1]
const level = parseInt(token.tag.slice(1))
+ let end = undefined
for (let i = idx + 1; i < tokens.length; i++) {
if (parseInt(tokens[i].tag.slice(1)) <= level) {
end = tokens[i].map![0]
break
}
}
+ regions.push({ start, end } as any)
}
}
- content = lines.slice(start, end).join('\n')
+ if (regions.length > 0) {
+ content = regions
+ .flatMap((region) => lines.slice(region.start, region.end))
+ .join('\n')
+ } else {
+ content = `No region or heading #${region} found in path: ${includePath}`
+ }
}
if (range) {
@@ -97,11 +105,9 @@ export function processIncludes(
includes,
cleanUrls
)
-
- //
} catch (error) {
if (process.env.DEBUG) {
- process.stderr.write(c.yellow(`\nInclude file not found: ${m1}`))
+ process.stderr.write(c.yellow(`Include file not found: ${m1}\n`))
}
return m // silently ignore error if file is not present