/** * 코드 펜스 시작 줄을 파싱한다. * @param {string} line - 마크다운 줄 * @returns {{ language: string, showLineNumbers: boolean }|null} 파싱 결과 */ export const parseCodeFenceLine = (line) => { const trimmed = String(line ?? '').trim() if (!trimmed.startsWith('```')) { return null } const rest = trimmed.slice(3).trim() if (!rest) { return { language: '', showLineNumbers: true } } const noLineNumberTokens = ['nolinenos', 'no-linenos', 'no-line-numbers'] const tokens = rest.split(/\s+/).filter(Boolean) const showLineNumbers = !tokens.some((token) => noLineNumberTokens.includes(token.toLowerCase())) const languageTokens = tokens.filter((token) => !noLineNumberTokens.includes(token.toLowerCase())) const language = languageTokens[0] || '' return { language, showLineNumbers } } /** * 코드 펜스 시작 줄을 만든다. * @param {{ language?: string, showLineNumbers?: boolean }} options - 옵션 * @returns {string} 펜스 시작 줄 */ export const buildCodeFenceOpener = (options = {}) => { const language = String(options.language ?? '').trim() const showLineNumbers = options.showLineNumbers !== false let opener = '```' if (language) { opener += language } if (!showLineNumbers) { opener += language ? ' nolinenos' : 'nolinenos' } return opener } /** * 코드 블록 마크다운 줄 배열을 만든다. * @param {{ language?: string, showLineNumbers?: boolean, body?: string }} options - 옵션 * @returns {string[]} 마크다운 줄 */ export const buildCodeBlockLines = (options = {}) => { const body = String(options.body ?? '').replace(/\r/g, '') const bodyLines = body.length ? body.split('\n') : [''] return [ buildCodeFenceOpener(options), ...bodyLines, '```' ] }