11import MarkdownIt from "markdown-it" ;
22
3- /**
4- * Extract the content under a heading titled "Mode: <mode>" (case-insensitive).
5- * - Matches any heading level (#..######)
6- * - Returns raw markdown content between this heading and the next heading
7- * of the same or higher level in the same document
8- * - If multiple sections match, the first one wins
9- * - The heading line itself is excluded from the returned content
10- */
11- export function extractModeSection ( markdown : string , mode : string ) : string | null {
12- if ( ! markdown || ! mode ) return null ;
3+ type HeadingMatcher = ( headingText : string , level : number ) => boolean ;
4+
5+ function extractSectionByHeading (
6+ markdown : string ,
7+ headingMatcher : HeadingMatcher
8+ ) : string | null {
9+ if ( ! markdown ) return null ;
1310
1411 const md = new MarkdownIt ( { html : false , linkify : false , typographer : false } ) ;
1512 const tokens = md . parse ( markdown , { } ) ;
1613 const lines = markdown . split ( / \r ? \n / ) ;
17- const target = `mode: ${ mode } ` . toLowerCase ( ) ;
1814
1915 for ( let i = 0 ; i < tokens . length ; i ++ ) {
20- const t = tokens [ i ] ;
21- if ( t . type !== "heading_open" ) continue ;
16+ const token = tokens [ i ] ;
17+ if ( token . type !== "heading_open" ) continue ;
2218
23- const level = Number ( t . tag ?. replace ( / ^ h / , "" ) ) || 1 ;
19+ const level = Number ( token . tag ?. replace ( / ^ h / , "" ) ) || 1 ;
2420 const inline = tokens [ i + 1 ] ;
2521 if ( inline ?. type !== "inline" ) continue ;
2622
27- const text = ( inline . content || "" ) . trim ( ) . toLowerCase ( ) ;
28- if ( text !== target ) continue ;
23+ const headingText = ( inline . content || "" ) . trim ( ) ;
24+ if ( ! headingMatcher ( headingText , level ) ) continue ;
2925
30- // Start content after the heading block ends
31- const headingEndLine = inline . map ?. [ 1 ] ?? t . map ?. [ 1 ] ?? ( t . map ?. [ 0 ] ?? 0 ) + 1 ;
26+ const headingEndLine = inline . map ?. [ 1 ] ?? token . map ?. [ 1 ] ?? ( token . map ?. [ 0 ] ?? 0 ) + 1 ;
3227
33- // Find the next heading of same or higher level to bound the section
34- let endLine = lines . length ; // exclusive
28+ let endLine = lines . length ;
3529 for ( let j = i + 1 ; j < tokens . length ; j ++ ) {
36- const tt = tokens [ j ] ;
37- if ( tt . type === "heading_open" ) {
38- const nextLevel = Number ( tt . tag ?. replace ( / ^ h / , "" ) ) || 1 ;
30+ const nextToken = tokens [ j ] ;
31+ if ( nextToken . type === "heading_open" ) {
32+ const nextLevel = Number ( nextToken . tag ?. replace ( / ^ h / , "" ) ) || 1 ;
3933 if ( nextLevel <= level ) {
40- endLine = tt . map ?. [ 0 ] ?? endLine ;
34+ endLine = nextToken . map ?. [ 0 ] ?? endLine ;
4135 break ;
4236 }
4337 }
@@ -50,6 +44,16 @@ export function extractModeSection(markdown: string, mode: string): string | nul
5044 return null ;
5145}
5246
47+ /**
48+ * Extract the content under a heading titled "Mode: <mode>" (case-insensitive).
49+ */
50+ export function extractModeSection ( markdown : string , mode : string ) : string | null {
51+ if ( ! markdown || ! mode ) return null ;
52+
53+ const expectedHeading = `mode: ${ mode } ` . toLowerCase ( ) ;
54+ return extractSectionByHeading ( markdown , ( headingText ) => headingText . toLowerCase ( ) === expectedHeading ) ;
55+ }
56+
5357/**
5458 * Extract the first section whose heading matches "Model: <regex>" and whose regex matches
5559 * the provided model identifier. Matching is case-insensitive by default unless the regex
@@ -58,16 +62,12 @@ export function extractModeSection(markdown: string, mode: string): string | nul
5862export function extractModelSection ( markdown : string , modelId : string ) : string | null {
5963 if ( ! markdown || ! modelId ) return null ;
6064
61- const md = new MarkdownIt ( { html : false , linkify : false , typographer : false } ) ;
62- const tokens = md . parse ( markdown , { } ) ;
63- const lines = markdown . split ( / \r ? \n / ) ;
6465 const headingPattern = / ^ m o d e l : \s * ( .+ ) $ / i;
6566
6667 const compileRegex = ( pattern : string ) : RegExp | null => {
6768 const trimmed = pattern . trim ( ) ;
6869 if ( ! trimmed ) return null ;
6970
70- // Allow optional /pattern/flags syntax; default to case-insensitive matching otherwise
7171 if ( trimmed . startsWith ( "/" ) && trimmed . lastIndexOf ( "/" ) > 0 ) {
7272 const lastSlash = trimmed . lastIndexOf ( "/" ) ;
7373 const source = trimmed . slice ( 1 , lastSlash ) ;
@@ -86,38 +86,10 @@ export function extractModelSection(markdown: string, modelId: string): string |
8686 }
8787 } ;
8888
89- for ( let i = 0 ; i < tokens . length ; i ++ ) {
90- const token = tokens [ i ] ;
91- if ( token . type !== "heading_open" ) continue ;
92-
93- const level = Number ( token . tag ?. replace ( / ^ h / , "" ) ) || 1 ;
94- const inline = tokens [ i + 1 ] ;
95- if ( inline ?. type !== "inline" ) continue ;
96-
97- const match = headingPattern . exec ( ( inline . content || "" ) . trim ( ) ) ;
98- if ( ! match ) continue ;
99-
89+ return extractSectionByHeading ( markdown , ( headingText ) => {
90+ const match = headingPattern . exec ( headingText ) ;
91+ if ( ! match ) return false ;
10092 const regex = compileRegex ( match [ 1 ] ?? "" ) ;
101- if ( ! regex ) continue ;
102- if ( ! regex . test ( modelId ) ) continue ;
103-
104- const headingEndLine = inline . map ?. [ 1 ] ?? token . map ?. [ 1 ] ?? ( token . map ?. [ 0 ] ?? 0 ) + 1 ;
105-
106- let endLine = lines . length ;
107- for ( let j = i + 1 ; j < tokens . length ; j ++ ) {
108- const nextToken = tokens [ j ] ;
109- if ( nextToken . type === "heading_open" ) {
110- const nextLevel = Number ( nextToken . tag ?. replace ( / ^ h / , "" ) ) || 1 ;
111- if ( nextLevel <= level ) {
112- endLine = nextToken . map ?. [ 0 ] ?? endLine ;
113- break ;
114- }
115- }
116- }
117-
118- const slice = lines . slice ( headingEndLine , endLine ) . join ( "\n" ) . trim ( ) ;
119- return slice . length > 0 ? slice : null ;
120- }
121-
122- return null ;
93+ return Boolean ( regex && regex . test ( modelId ) ) ;
94+ } ) ;
12395}
0 commit comments