main
 1// Copyright 2025 Google LLC
 2//
 3// Licensed under the Apache License, Version 2.0 (the "License");
 4// you may not use this file except in compliance with the License.
 5// You may obtain a copy of the License at
 6//
 7//     https://www.apache.org/licenses/LICENSE-2.0
 8//
 9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import { type Buffer } from 'node:buffer'
16import { bufToString } from './util.ts'
17
18export function transformMarkdown(buf: Buffer | string): string {
19  const output = []
20  const tabRe = /^(  +|\t)/
21  const codeBlockRe =
22    /^(?<fence>(`{3,20}|~{3,20}))(?:(?<js>(js|javascript|ts|typescript))|(?<bash>(sh|shell|bash))|.*)$/
23  let state = 'root'
24  let codeBlockEnd = ''
25  let prevLineIsEmpty = true
26  for (const line of bufToString(buf).split(/\r?\n/)) {
27    switch (state) {
28      case 'root':
29        if (tabRe.test(line) && prevLineIsEmpty) {
30          output.push(line)
31          state = 'tab'
32          continue
33        }
34        const { fence, js, bash } = line.match(codeBlockRe)?.groups || {}
35        if (!fence) {
36          prevLineIsEmpty = line === ''
37          output.push('// ' + line)
38          continue
39        }
40        codeBlockEnd = fence
41        if (js) {
42          state = 'js'
43          output.push('')
44        } else if (bash) {
45          state = 'bash'
46          output.push('await $`')
47        } else {
48          state = 'other'
49          output.push('')
50        }
51        break
52      case 'tab':
53        if (line === '') {
54          output.push('')
55        } else if (tabRe.test(line)) {
56          output.push(line)
57        } else {
58          output.push('// ' + line)
59          state = 'root'
60        }
61        break
62      case 'js':
63        if (line === codeBlockEnd) {
64          output.push('')
65          state = 'root'
66        } else {
67          output.push(line)
68        }
69        break
70      case 'bash':
71        if (line === codeBlockEnd) {
72          output.push('`')
73          state = 'root'
74        } else {
75          output.push(line)
76        }
77        break
78      case 'other':
79        if (line === codeBlockEnd) {
80          output.push('')
81          state = 'root'
82        } else {
83          output.push('// ' + line)
84        }
85        break
86    }
87  }
88  return output.join('\n')
89}