feat(macro): add $include to include other document fragments

This commit is contained in:
purocean
2021-11-13 10:31:52 +08:00
parent ac64ca7088
commit e22fc330c8
+74 -25
View File
@@ -1,40 +1,44 @@
import frontMatter from 'front-matter'
import type { Plugin } from '@fe/context'
import { render } from '@fe/services/view'
import { readFile } from '@fe/support/api'
import type { Doc } from '@fe/types'
import { getLogger, md5 } from '@fe/utils'
import { dirname, resolve } from '@fe/utils/path'
const logger = getLogger('macro')
type Result = { __macroResult: true, vars?: Record<string, any>, toString: () => string }
type Expression = { id: string, match: string, exp: string, vars: Record<string, any> }
const logger = getLogger('plugin-macro')
const AsyncFunction = Object.getPrototypeOf(async () => 0).constructor
let macroCache: Record<string, Record<string, string>> = {}
let macroCache: Record<string, Record<string, Result>> = {}
function checkMacroResult (result: any) {
if (typeof result === 'object' ||
if (
result === null ||
typeof result === 'function' ||
typeof result === 'symbol' ||
typeof result === 'undefined'
typeof result === 'undefined' ||
(typeof result === 'object' && (!result.__macroResult))
) {
throw new Error('Macro result type error.')
}
}
function macro (match: string, vars: Record<string, any>, cache: Record<string, string>) {
const expression = match
.substring(2, match.length - 2)
.trim()
.replace(/(?:=\\>|<\\=)/g, x => x.replace('\\', ''))
function macro (expression: Expression, cache: Record<string, Result>): Result {
logger.debug('macro', expression)
const id = md5(expression)
logger.debug(id, expression, vars)
const { id, match, exp, vars } = expression
// async expression result cache
if (id in cache) {
return cache[id]
}
const FunctionConstructor = expression.startsWith('await') ? AsyncFunction : Function
const FunctionConstructor = exp.startsWith('await') ? AsyncFunction : Function
const fun = new FunctionConstructor('vars', `with (vars) { return ${expression}; }`)
const fun = new FunctionConstructor('vars', `with (vars) { return ${exp}; }`)
const result = fun(vars)
@@ -43,20 +47,20 @@ function macro (match: string, vars: Record<string, any>, cache: Record<string,
checkMacroResult(res)
return res
}).catch(e => {
logger.error(id, e)
logger.error('macro', id, e)
return match
}).then(res => {
cache[id] = res
logger.debug('async', id, 'rerender')
logger.debug('macro', 'async', id, 'rerender')
render()
})
return 'macro is running……'
return { __macroResult: true, toString: () => 'macro is running……' }
}
checkMacroResult(result)
return '' + result
return result
}
function lineCount (str: string) {
@@ -72,9 +76,36 @@ function lineCount (str: string) {
return s
}
async function include (belongDoc: Doc | undefined | null, path: string): Promise<Result> {
if (!belongDoc) {
throw new Error('Current document is null')
}
try {
const absolutePath = resolve(dirname(belongDoc.path), path)
const { content } = await readFile({ repo: belongDoc.repo, path: absolutePath })
const fm = frontMatter(content)
// merge front-matter attributes to current document vars.
const vars = {}
if (fm.attributes && typeof fm.attributes === 'object') {
Object.assign(vars, fm.attributes)
}
return { __macroResult: true, vars, toString: () => fm.body }
} catch (error: any) {
return error.message
}
}
export default {
name: 'markdown-macro',
register: ctx => {
// clear cache after view refresh
ctx.registerHook('VIEW_BEFORE_REFRESH', () => {
macroCache = {}
})
ctx.markdown.registerPlugin(md => {
md.core.ruler.after('normalize', 'macro', (state) => {
const env = state.env || {}
@@ -84,13 +115,14 @@ export default {
}
const file = env.file || {}
const vars = {
const vars: Record<string, any> = {
$include: include.bind(null, file),
$ctx: ctx,
$doc: {
basename: file.name ? file.name.substring(0, file.name.lastIndexOf('.')) : '',
...ctx.lib.lodash.pick(env.file, 'name', 'repo', 'path', 'content', 'status')
},
...env.attributes
...env.attributes,
}
if (!env.macroLines) {
@@ -106,14 +138,31 @@ export default {
let posOffset = 0
state.src = state.src.replace(reg, (match, matchPos) => {
try {
const result = macro(match, vars, macroCache[cacheKey])
console.log('xxx', match, { ...vars })
const exp = match
.substring(2, match.length - 2)
.trim()
.replace(/(?:=\\>|<\\=)/g, x => x.replace('\\', ''))
const id = md5(exp)
const expression = { id, match, exp, vars }
const result = macro(expression, macroCache[cacheKey])
if (result && result.__macroResult && result.vars) {
Object.assign(vars, result.vars)
}
// result maybe string or other type.
const resultStr = '' + result
const matchLine = lineCount(match)
const resultLine = lineCount(result)
const resultLine = lineCount(resultStr)
if (resultLine !== matchLine) {
const currentLineOffset = (matchLine - resultLine)
const currentPosOffset = (match.length - result.length)
const currentPosOffset = (match.length - resultStr.length)
lineOffset += currentLineOffset
posOffset += currentPosOffset
env.macroLines.push({
@@ -125,11 +174,11 @@ export default {
currentPosOffset,
currentLineOffset,
matchLength: match.length,
resultLength: result.length,
resultLength: resultStr.length,
})
}
return result
return resultStr
} catch (error) {}
return match