(section: Section)
| 127 | } |
| 128 | |
| 129 | function extractExecutableBlocks(section: Section): ScriptBlock[] { |
| 130 | const lines = section.content.split(/\r?\n/) |
| 131 | const blocks: ScriptBlock[] = [] |
| 132 | let activeFence: string | null = null |
| 133 | let activeLanguage = '' |
| 134 | let activeStartLine = 0 |
| 135 | let buffer: string[] = [] |
| 136 | |
| 137 | for (let index = 0; index < lines.length; index += 1) { |
| 138 | const line = lines[index] ?? '' |
| 139 | const fenceMatch = line.match(/^(```|~~~)\s*([^\s`]*)?.*$/) |
| 140 | |
| 141 | if (!activeFence) { |
| 142 | if (!fenceMatch) { |
| 143 | continue |
| 144 | } |
| 145 | activeFence = fenceMatch[1] ?? '```' |
| 146 | activeLanguage = (fenceMatch[2] ?? '').trim() |
| 147 | activeStartLine = section.startLine + index + 1 |
| 148 | buffer = [] |
| 149 | continue |
| 150 | } |
| 151 | |
| 152 | if (line.trim() === activeFence) { |
| 153 | const code = buffer.join('\n').trim() |
| 154 | if (code && isExecutableBlockLanguage(activeLanguage)) { |
| 155 | blocks.push({ |
| 156 | code, |
| 157 | language: activeLanguage, |
| 158 | startLine: activeStartLine, |
| 159 | }) |
| 160 | } |
| 161 | activeFence = null |
| 162 | activeLanguage = '' |
| 163 | buffer = [] |
| 164 | continue |
| 165 | } |
| 166 | |
| 167 | buffer.push(line) |
| 168 | } |
| 169 | |
| 170 | return blocks |
| 171 | } |
| 172 | |
| 173 | function resolveShellForLanguage(language: string): string { |
| 174 | const normalized = language.trim().toLowerCase() |
no test coverage detected