@incremark/core 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +99 -0
- package/dist/detector/index.d.ts +4 -0
- package/dist/detector/index.js +155 -0
- package/dist/detector/index.js.map +1 -0
- package/dist/index-i_qABRHQ.d.ts +207 -0
- package/dist/index.d.ts +89 -0
- package/dist/index.js +515 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/index.d.ts +22 -0
- package/dist/utils/index.js +25 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +59 -0
- package/src/detector/index.test.ts +150 -0
- package/src/detector/index.ts +271 -0
- package/src/index.ts +51 -0
- package/src/parser/IncremarkParser.comprehensive.test.ts +418 -0
- package/src/parser/IncremarkParser.robustness.test.ts +428 -0
- package/src/parser/IncremarkParser.test.ts +110 -0
- package/src/parser/IncremarkParser.ts +476 -0
- package/src/parser/index.ts +2 -0
- package/src/types/index.ts +144 -0
- package/src/utils/index.ts +44 -0
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@incremark/core",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "增量式 Markdown 解析器核心库",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./detector": {
|
|
15
|
+
"types": "./dist/detector/index.d.ts",
|
|
16
|
+
"import": "./dist/detector/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./utils": {
|
|
19
|
+
"types": "./dist/utils/index.d.ts",
|
|
20
|
+
"import": "./dist/utils/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"src"
|
|
26
|
+
],
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@types/mdast": "^4.0.0",
|
|
29
|
+
"mdast-util-from-markdown": "^2.0.0",
|
|
30
|
+
"mdast-util-gfm": "^3.0.0",
|
|
31
|
+
"micromark-extension-gfm": "^3.0.0",
|
|
32
|
+
"micromark-util-types": "^2.0.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"tsup": "^8.0.0",
|
|
36
|
+
"typescript": "^5.3.0"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"markdown",
|
|
40
|
+
"parser",
|
|
41
|
+
"incremental",
|
|
42
|
+
"streaming",
|
|
43
|
+
"ai",
|
|
44
|
+
"mdast",
|
|
45
|
+
"unified"
|
|
46
|
+
],
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "https://github.com/user/incremark.git",
|
|
51
|
+
"directory": "packages/core"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsup",
|
|
55
|
+
"dev": "tsup --watch",
|
|
56
|
+
"test": "vitest",
|
|
57
|
+
"test:run": "vitest run"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
detectFenceStart,
|
|
4
|
+
detectFenceEnd,
|
|
5
|
+
isEmptyLine,
|
|
6
|
+
isHeading,
|
|
7
|
+
isThematicBreak,
|
|
8
|
+
isListItemStart,
|
|
9
|
+
isBlockquoteStart,
|
|
10
|
+
detectContainer,
|
|
11
|
+
createInitialContext
|
|
12
|
+
} from './index'
|
|
13
|
+
|
|
14
|
+
describe('块检测器', () => {
|
|
15
|
+
describe('detectFenceStart', () => {
|
|
16
|
+
it('检测反引号 fence', () => {
|
|
17
|
+
const result = detectFenceStart('```js')
|
|
18
|
+
expect(result).toEqual({ char: '`', length: 3 })
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('检测波浪号 fence', () => {
|
|
22
|
+
const result = detectFenceStart('~~~python')
|
|
23
|
+
expect(result).toEqual({ char: '~', length: 3 })
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('检测更长的 fence', () => {
|
|
27
|
+
const result = detectFenceStart('`````')
|
|
28
|
+
expect(result).toEqual({ char: '`', length: 5 })
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('非 fence 返回 null', () => {
|
|
32
|
+
expect(detectFenceStart('普通文本')).toBeNull()
|
|
33
|
+
expect(detectFenceStart('``不够长')).toBeNull()
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
describe('detectFenceEnd', () => {
|
|
38
|
+
it('匹配相同长度', () => {
|
|
39
|
+
const context = { ...createInitialContext(), inFencedCode: true, fenceChar: '`', fenceLength: 3 }
|
|
40
|
+
expect(detectFenceEnd('```', context)).toBe(true)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('匹配更长的结束', () => {
|
|
44
|
+
const context = { ...createInitialContext(), inFencedCode: true, fenceChar: '`', fenceLength: 3 }
|
|
45
|
+
expect(detectFenceEnd('`````', context)).toBe(true)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('不匹配更短的', () => {
|
|
49
|
+
const context = { ...createInitialContext(), inFencedCode: true, fenceChar: '`', fenceLength: 5 }
|
|
50
|
+
expect(detectFenceEnd('```', context)).toBe(false)
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
describe('isEmptyLine', () => {
|
|
55
|
+
it('空行', () => {
|
|
56
|
+
expect(isEmptyLine('')).toBe(true)
|
|
57
|
+
expect(isEmptyLine(' ')).toBe(true)
|
|
58
|
+
expect(isEmptyLine('\t')).toBe(true)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('非空行', () => {
|
|
62
|
+
expect(isEmptyLine('内容')).toBe(false)
|
|
63
|
+
expect(isEmptyLine(' a ')).toBe(false)
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
describe('isHeading', () => {
|
|
68
|
+
it('有效标题', () => {
|
|
69
|
+
expect(isHeading('# H1')).toBe(true)
|
|
70
|
+
expect(isHeading('## H2')).toBe(true)
|
|
71
|
+
expect(isHeading('###### H6')).toBe(true)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('无效标题', () => {
|
|
75
|
+
expect(isHeading('####### H7')).toBe(false) // 超过 6 级
|
|
76
|
+
expect(isHeading('#没有空格')).toBe(false)
|
|
77
|
+
expect(isHeading('普通文本')).toBe(false)
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
describe('isThematicBreak', () => {
|
|
82
|
+
it('有效水平线', () => {
|
|
83
|
+
expect(isThematicBreak('---')).toBe(true)
|
|
84
|
+
expect(isThematicBreak('***')).toBe(true)
|
|
85
|
+
expect(isThematicBreak('___')).toBe(true)
|
|
86
|
+
expect(isThematicBreak('-----')).toBe(true)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('无效水平线', () => {
|
|
90
|
+
expect(isThematicBreak('--')).toBe(false)
|
|
91
|
+
expect(isThematicBreak('- - -')).toBe(false) // 有空格
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
describe('isListItemStart', () => {
|
|
96
|
+
it('无序列表', () => {
|
|
97
|
+
expect(isListItemStart('- item')).toEqual({ ordered: false, indent: 0 })
|
|
98
|
+
expect(isListItemStart(' * item')).toEqual({ ordered: false, indent: 2 })
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('有序列表', () => {
|
|
102
|
+
expect(isListItemStart('1. item')).toEqual({ ordered: true, indent: 0 })
|
|
103
|
+
expect(isListItemStart(' 99. item')).toEqual({ ordered: true, indent: 2 })
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('非列表', () => {
|
|
107
|
+
expect(isListItemStart('普通文本')).toBeNull()
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
describe('isBlockquoteStart', () => {
|
|
112
|
+
it('有效引用', () => {
|
|
113
|
+
expect(isBlockquoteStart('> quote')).toBe(true)
|
|
114
|
+
expect(isBlockquoteStart(' > quote')).toBe(true)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('无效引用', () => {
|
|
118
|
+
expect(isBlockquoteStart(' > 太多缩进')).toBe(false)
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
describe('detectContainer', () => {
|
|
123
|
+
it('检测容器开始', () => {
|
|
124
|
+
const result = detectContainer('::: warning')
|
|
125
|
+
expect(result).toEqual({ name: 'warning', markerLength: 3, isEnd: false })
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('检测容器结束', () => {
|
|
129
|
+
const result = detectContainer(':::')
|
|
130
|
+
expect(result).toEqual({ name: '', markerLength: 3, isEnd: true })
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('检测更长的标记', () => {
|
|
134
|
+
const result = detectContainer('::::: outer')
|
|
135
|
+
expect(result).toEqual({ name: 'outer', markerLength: 5, isEnd: false })
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('名称白名单', () => {
|
|
139
|
+
const config = { allowedNames: ['warning', 'info'] }
|
|
140
|
+
expect(detectContainer('::: warning', config)).not.toBeNull()
|
|
141
|
+
expect(detectContainer('::: danger', config)).toBeNull()
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('非容器语法', () => {
|
|
145
|
+
expect(detectContainer('普通文本')).toBeNull()
|
|
146
|
+
expect(detectContainer(':: 太短')).toBeNull()
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
})
|
|
150
|
+
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 块类型检测与边界判断
|
|
3
|
+
*
|
|
4
|
+
* Markdown 块级元素的识别规则
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { BlockContext, ContainerConfig, ContainerMatch } from '../types'
|
|
8
|
+
|
|
9
|
+
// ============ 代码块检测 ============
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 检测行是否是代码块 fence 开始
|
|
13
|
+
*/
|
|
14
|
+
export function detectFenceStart(line: string): { char: string; length: number } | null {
|
|
15
|
+
const match = line.match(/^(\s*)((`{3,})|(~{3,}))/)
|
|
16
|
+
if (match) {
|
|
17
|
+
const fence = match[2]
|
|
18
|
+
const char = fence[0]
|
|
19
|
+
return { char, length: fence.length }
|
|
20
|
+
}
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 检测行是否是代码块 fence 结束
|
|
26
|
+
*/
|
|
27
|
+
export function detectFenceEnd(line: string, context: BlockContext): boolean {
|
|
28
|
+
if (!context.inFencedCode || !context.fenceChar || !context.fenceLength) {
|
|
29
|
+
return false
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const pattern = new RegExp(`^\\s{0,3}${context.fenceChar}{${context.fenceLength},}\\s*$`)
|
|
33
|
+
return pattern.test(line)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ============ 行类型检测 ============
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 检测是否是空行或仅包含空白字符
|
|
40
|
+
*/
|
|
41
|
+
export function isEmptyLine(line: string): boolean {
|
|
42
|
+
return /^\s*$/.test(line)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 检测是否是标题行
|
|
47
|
+
*/
|
|
48
|
+
export function isHeading(line: string): boolean {
|
|
49
|
+
return /^#{1,6}\s/.test(line)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 检测是否是 thematic break(水平线)
|
|
54
|
+
*/
|
|
55
|
+
export function isThematicBreak(line: string): boolean {
|
|
56
|
+
return /^(\*{3,}|-{3,}|_{3,})\s*$/.test(line.trim())
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 检测是否是列表项开始
|
|
61
|
+
*/
|
|
62
|
+
export function isListItemStart(line: string): { ordered: boolean; indent: number } | null {
|
|
63
|
+
// 无序列表: - * +
|
|
64
|
+
const unordered = line.match(/^(\s*)([-*+])\s/)
|
|
65
|
+
if (unordered) {
|
|
66
|
+
return { ordered: false, indent: unordered[1].length }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 有序列表: 1. 2) 等
|
|
70
|
+
const ordered = line.match(/^(\s*)(\d{1,9})[.)]\s/)
|
|
71
|
+
if (ordered) {
|
|
72
|
+
return { ordered: true, indent: ordered[1].length }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return null
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 检测是否是引用块开始
|
|
80
|
+
*/
|
|
81
|
+
export function isBlockquoteStart(line: string): boolean {
|
|
82
|
+
return /^\s{0,3}>/.test(line)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 检测是否是 HTML 块
|
|
87
|
+
*/
|
|
88
|
+
export function isHtmlBlock(line: string): boolean {
|
|
89
|
+
return (
|
|
90
|
+
/^\s{0,3}<(script|pre|style|textarea|!--|!DOCTYPE|\?|!\[CDATA\[)/i.test(line) ||
|
|
91
|
+
/^\s{0,3}<\/?[a-zA-Z][a-zA-Z0-9-]*(\s|>|$)/.test(line)
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 检测表格分隔行
|
|
97
|
+
*/
|
|
98
|
+
export function isTableDelimiter(line: string): boolean {
|
|
99
|
+
return /^\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?$/.test(line.trim())
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ============ 容器检测 ============
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 检测容器开始或结束
|
|
106
|
+
*
|
|
107
|
+
* 支持格式:
|
|
108
|
+
* - ::: name 开始
|
|
109
|
+
* - ::: name attr 开始(带属性)
|
|
110
|
+
* - ::: 结束
|
|
111
|
+
* - :::::: name 开始(更长的标记,用于嵌套)
|
|
112
|
+
*/
|
|
113
|
+
export function detectContainer(line: string, config?: ContainerConfig): ContainerMatch | null {
|
|
114
|
+
const marker = config?.marker || ':'
|
|
115
|
+
const minLength = config?.minMarkerLength || 3
|
|
116
|
+
|
|
117
|
+
const escapedMarker = marker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
118
|
+
const pattern = new RegExp(
|
|
119
|
+
`^(\\s*)(${escapedMarker}{${minLength},})(?:\\s+(\\w[\\w-]*))?(?:\\s+(.*))?\\s*$`
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
const match = line.match(pattern)
|
|
123
|
+
if (!match) {
|
|
124
|
+
return null
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const markerLength = match[2].length
|
|
128
|
+
const name = match[3] || ''
|
|
129
|
+
const isEnd = !name && !match[4]
|
|
130
|
+
|
|
131
|
+
if (!isEnd && config?.allowedNames && config.allowedNames.length > 0) {
|
|
132
|
+
if (!config.allowedNames.includes(name)) {
|
|
133
|
+
return null
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return { name, markerLength, isEnd }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 检测容器结束
|
|
142
|
+
*/
|
|
143
|
+
export function detectContainerEnd(
|
|
144
|
+
line: string,
|
|
145
|
+
context: BlockContext,
|
|
146
|
+
config?: ContainerConfig
|
|
147
|
+
): boolean {
|
|
148
|
+
if (!context.inContainer || !context.containerMarkerLength) {
|
|
149
|
+
return false
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const result = detectContainer(line, config)
|
|
153
|
+
if (!result) {
|
|
154
|
+
return false
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return result.isEnd && result.markerLength >= context.containerMarkerLength
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ============ 边界检测 ============
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 判断两行之间是否构成块边界
|
|
164
|
+
*/
|
|
165
|
+
export function isBlockBoundary(
|
|
166
|
+
prevLine: string,
|
|
167
|
+
currentLine: string,
|
|
168
|
+
context: BlockContext
|
|
169
|
+
): boolean {
|
|
170
|
+
if (context.inFencedCode) {
|
|
171
|
+
return detectFenceEnd(currentLine, context)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (isEmptyLine(prevLine) && !isEmptyLine(currentLine)) {
|
|
175
|
+
return true
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (isHeading(currentLine) && !isEmptyLine(prevLine)) {
|
|
179
|
+
return true
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (isThematicBreak(currentLine)) {
|
|
183
|
+
return true
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (detectFenceStart(currentLine)) {
|
|
187
|
+
return true
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return false
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ============ 上下文管理 ============
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* 创建初始上下文
|
|
197
|
+
*/
|
|
198
|
+
export function createInitialContext(): BlockContext {
|
|
199
|
+
return {
|
|
200
|
+
inFencedCode: false,
|
|
201
|
+
listDepth: 0,
|
|
202
|
+
blockquoteDepth: 0,
|
|
203
|
+
inContainer: false,
|
|
204
|
+
containerDepth: 0
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* 更新上下文(处理一行后)
|
|
210
|
+
*/
|
|
211
|
+
export function updateContext(
|
|
212
|
+
line: string,
|
|
213
|
+
context: BlockContext,
|
|
214
|
+
containerConfig?: ContainerConfig | boolean
|
|
215
|
+
): BlockContext {
|
|
216
|
+
const newContext = { ...context }
|
|
217
|
+
|
|
218
|
+
const containerCfg =
|
|
219
|
+
containerConfig === true ? {} : containerConfig === false ? undefined : containerConfig
|
|
220
|
+
|
|
221
|
+
// 代码块优先级最高
|
|
222
|
+
if (context.inFencedCode) {
|
|
223
|
+
if (detectFenceEnd(line, context)) {
|
|
224
|
+
newContext.inFencedCode = false
|
|
225
|
+
newContext.fenceChar = undefined
|
|
226
|
+
newContext.fenceLength = undefined
|
|
227
|
+
}
|
|
228
|
+
return newContext
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const fence = detectFenceStart(line)
|
|
232
|
+
if (fence) {
|
|
233
|
+
newContext.inFencedCode = true
|
|
234
|
+
newContext.fenceChar = fence.char
|
|
235
|
+
newContext.fenceLength = fence.length
|
|
236
|
+
return newContext
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// 容器处理
|
|
240
|
+
if (containerCfg !== undefined) {
|
|
241
|
+
if (context.inContainer) {
|
|
242
|
+
if (detectContainerEnd(line, context, containerCfg)) {
|
|
243
|
+
newContext.containerDepth = context.containerDepth - 1
|
|
244
|
+
if (newContext.containerDepth === 0) {
|
|
245
|
+
newContext.inContainer = false
|
|
246
|
+
newContext.containerMarkerLength = undefined
|
|
247
|
+
newContext.containerName = undefined
|
|
248
|
+
}
|
|
249
|
+
return newContext
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const nested = detectContainer(line, containerCfg)
|
|
253
|
+
if (nested && !nested.isEnd) {
|
|
254
|
+
newContext.containerDepth = context.containerDepth + 1
|
|
255
|
+
return newContext
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
const container = detectContainer(line, containerCfg)
|
|
259
|
+
if (container && !container.isEnd) {
|
|
260
|
+
newContext.inContainer = true
|
|
261
|
+
newContext.containerMarkerLength = container.markerLength
|
|
262
|
+
newContext.containerName = container.name
|
|
263
|
+
newContext.containerDepth = 1
|
|
264
|
+
return newContext
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return newContext
|
|
270
|
+
}
|
|
271
|
+
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @incremark/core
|
|
3
|
+
*
|
|
4
|
+
* 增量式 Markdown 解析器核心库
|
|
5
|
+
* 专为 AI 流式输出场景设计
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// 核心解析器
|
|
9
|
+
export { IncremarkParser, createIncremarkParser } from './parser'
|
|
10
|
+
|
|
11
|
+
// 类型导出
|
|
12
|
+
export type {
|
|
13
|
+
BlockStatus,
|
|
14
|
+
ParsedBlock,
|
|
15
|
+
IncrementalUpdate,
|
|
16
|
+
ParserOptions,
|
|
17
|
+
ParserState,
|
|
18
|
+
ContainerConfig,
|
|
19
|
+
BlockContext,
|
|
20
|
+
ContainerMatch,
|
|
21
|
+
BlockTypeInfo,
|
|
22
|
+
Root,
|
|
23
|
+
RootContent
|
|
24
|
+
} from './types'
|
|
25
|
+
|
|
26
|
+
// 检测器
|
|
27
|
+
export {
|
|
28
|
+
// 代码块
|
|
29
|
+
detectFenceStart,
|
|
30
|
+
detectFenceEnd,
|
|
31
|
+
// 行类型
|
|
32
|
+
isEmptyLine,
|
|
33
|
+
isHeading,
|
|
34
|
+
isThematicBreak,
|
|
35
|
+
isListItemStart,
|
|
36
|
+
isBlockquoteStart,
|
|
37
|
+
isHtmlBlock,
|
|
38
|
+
isTableDelimiter,
|
|
39
|
+
// 容器
|
|
40
|
+
detectContainer,
|
|
41
|
+
detectContainerEnd,
|
|
42
|
+
// 边界
|
|
43
|
+
isBlockBoundary,
|
|
44
|
+
// 上下文
|
|
45
|
+
createInitialContext,
|
|
46
|
+
updateContext
|
|
47
|
+
} from './detector'
|
|
48
|
+
|
|
49
|
+
// 工具函数
|
|
50
|
+
export { generateId, resetIdCounter, calculateLineOffset, splitLines, joinLines } from './utils'
|
|
51
|
+
|