bashup/mdsh

基于 Markdown 的多语言脚本编程

安装: 9

依赖项: 0

建议者: 0

安全: 0

星标: 174

关注者: 7

分支: 15

开放问题: 0

语言:Shell

dev-master 2022-07-01 11:01 UTC

This package is auto-updated.

Last update: 2024-09-29 05:01:54 UTC


README

mdsh 是一个 Markdown 文件编译器和解释器,用于 Bash 脚本。它可以用于 #! 行来使 Markdown 文件可执行,或者可以作为独立工具从 Markdown 文件生成无依赖、可分发的 Bash 脚本。

默认情况下,mdsh 只考虑 shell 代码块为 Bash 代码,但您也可以使用 @mdsh 块来定义其他语言的处理器。例如,此脚本将通过管道将 python 标记的代码块传递给 python 命令来运行它们

#!/usr/bin/env mdsh

# Hello World in Python

The following code block is executed at compile time (due to the `@mdsh`).
(The first word on the opening line could be `shell` or `sh` or anything
else, as long as the second word is `@mdsh`.)

```bash @mdsh
mdsh-lang-python() { python; }
```

Now that we've defined a language handler for `python`, this next code
block is translated to shell code that runs python with the block's
contents on stdin:

```python
print("hello world!")
```

运行上述 Markdown 文件产生与以下等效 Bash 脚本相同的结果

#!/usr/bin/env bash
{ python; } <<'```'
print("hello world!")
```

mdsh 支持处理任何您可以为它编写 Bash 代码片段的语言块,甚至允许您编写 "编译时" 代码来将包含元数据或 DSL 片段的块转换为 Bash 代码。结果可以是实时执行以进行开发,也可以通过 mdsh --compile 部署/分发。编译后的脚本不包含任何 @mdsh 代码,也没有任何隐藏的运行时依赖项:mdsh --compile 输出的所有内容都是您提供给它的代码或数据,或者由您提供的 Bash 代码生成的!

内容

安装

mdsh 可以以下方式之一安装

  • 使用 basher,通过 basher install bashup/mdsh
  • 使用 composer,通过 composer require bashup/mdsh:dev-master(将其添加到您的项目)或 composer global require bashup/mdsh:dev-master(全局安装)
  • 使用 git,通过克隆此存储库并将 bin/mdsh 文件复制或链接到您的 PATH 目录上,或者
  • 直接 下载脚本 到您的 PATH 目录,然后对它运行 chmod +x

用法

运行 mdsh markdownfile args... 将从 markdownfile 中读取并翻译无缩进、三重反引号围栏的代码块到 Bash 代码,基于块上列出的语言和任何您定义的翻译规则。然后运行生成的翻译脚本,将 args 作为脚本的参数传递。

标记为 shell 的块被视为 Bash 代码,并直接复制到翻译脚本中。因此,传递给 mdsh 的参数(在 Markdown 文件路径之后)可用作 $1$2 等,在 shell 块的顶层代码中,就像在常规 Bash 脚本中一样。

(通常,您不会直接运行mdsh,而是将#!/usr/bin/env mdsh放在Markdown文件的第一行,然后用chmod +x使其可执行。这样,您的脚本用户就不需要做任何特殊操作即可运行它。)

您还可以使用mdsh --compile 文件1 文件2...将一个或多个Markdown文件转换为bash代码,并将结果输出到标准输出。(-文件名表示“从标准输入读取”。)这可以用于调试,或者制作一个不需要用户拥有mdsh即可运行的脚本的可分发版本。

(还有一个mdsh --eval 文件名选项,与--compile类似,但只接受一个非stdin文件,并在文件末尾输出特殊代码以支持可源文件。请参阅下文关于制作可源脚本的部分以获取更多详细信息。)

--eval--compile都可以用--out 文件名开头,在这种情况下,如果编译或运行成功且没有错误,则替换文件名的内容。(mdsh的输出将在内存中缓冲,然后在成功完成后一次性输出。如果文件已存在,其权限将保持不变。)

数据块

默认情况下,不缩进的三重反引号块的内容被当作数据处理:其内容将被添加到按块上的语言命名的bash数组中,例如

# Data Arrays Example
Blocks without a defined language processor get translated to a variable
assignment like `mdsh_raw_json+=(\#\ block\ 0)` at that point in the
generated script:

```json
{ "hello": "world" }
```
```shell
echo "${mdsh_raw_json[0]}"   # prints '{ "hello": "world" }'
```
```json
{ "this is": "great" }
```
```shell
echo "${mdsh_raw_json[0]}"   # prints '{ "hello": "world" }'
echo "${mdsh_raw_json[1]}"   # prints '{ "this is": "great" }'
```

## Naming Rules
Language names are *case sensitive*, and non-identifier
characters in language names become `_` in variable names:

```C++
// hey
```
```shell
echo "${mdsh_raw_C__[0]}"   # prints '// hey'
```

当然,如果您能自动化这些块的处理,那就更好了,这样您就不需要每个块后面都跟着另一个shell块来处理它了!这就是为什么下一节是关于...

处理非 shell 语言

为了自动化处理非shell语言块,您可以定义一个或多个@mdsh块,包含“钩子函数”。@mdsh块有点像Makefile,因为它们定义了基于所使用的语言的如何构建脚本的部分的规则

这些构建规则通过定义特别命名的bash函数来指定。与shell块中的函数不同,这些函数不是您脚本的一部分,因此不能直接调用。相反,mdsh本身会在后续块的语言与函数名称之一匹配时调用它们(或复制它们的源代码)。

Markdown代码块的语言通常是开头的反引号后的一个单词。但如果出现多个单词,mdsh将认为语言是第二个单词(如果它以@开头),或者整行扁平化为单个变量名。一些示例翻译

函数名称的解析如下

  • mdsh-lang-X函数是当遇到语言X的代码块时要运行的代码模板。它的函数体被复制到翻译后的脚本中,作为一个bash复合语句(即在花括号{...}中),它将执行块内容作为标准输入。其标准输出与整体脚本的相同。
  • mdsh-compile-X函数在编译时调用,带有块内容作为$1,并必须将其输出到其标准输出的bash源代码转换。块的完整原始语言标签在$2中,代码块的起始行号在$3中。
  • 如果既不存在 mdsh-lang-X 函数也不存在 mdsh-compile-X 函数,则在编译时将调用 mdsh-misc,使用原始语言标记作为 $1,并将块内容作为 $2mdsh-misc 的输出将被添加到编译后的脚本中。(mdsh-misc 的默认实现将输出代码,用于保存块内容,如上所述的数据块部分所述。)
  • mdsh-after-X 函数是在遇到语言块 X 后要运行的代码的模板。其函数体被复制到翻译后的脚本中,作为 mdsh-lang-X 体、mdsh-compile-X 输出或 mdsh_raw_X+=(contents) 语句之后的块。它接收块源代码,所以其标准输入和输出是脚本的本身。

如果同时存在 mdsh-lang-Xmdsh-compile-X 函数,则 mdsh-lang-X 优先。定义其中的任何一个也禁用了 $mdsh_raw_X 功能:只有不可翻译的“数据”块被添加到数组中。

然而,如果没有 mdsh-lang-Xmdsh-compile-X,则 mdsh-after-X 函数可以读取最新块的内容,从 ${mdsh_raw_VARNAME[-1]} 中读取(除非您已替换默认的 mdsh-misc 实现)。如果您不取消设置数组,它将随着遇到更多该语言的块而不断增长。

注意:这些函数名是 区分大小写的,所以带有大写字母 C 的块不会触发与带有小写字母 c 的块相同的函数,反之亦然。此外,请注意,由于 mdsh 块是在编译时执行的,它们没有访问脚本的参数或 I/O:在这些块中,您只能定义钩子函数。

最后,请记住,您通常不应该在 @mdsh 块中放入任何代码,除非您有意进行元编程或代码生成。这是因为 @mdsh不是翻译脚本的一部分,它们是翻译过程的一部分。所以您在其中定义的任何函数在脚本实际运行时将不可用,并且您对变量所做的任何更改在脚本实际执行时也不会存在。

高级块编译技术

一旦您习惯了做一些 mdsh-lang-X 函数,为什么不尝试一些 mdsh-compile 呢?

例如,在jqmd 项目中,我最初有一些这样的代码

YAML() { JSON "$(echo "$1" | yaml2json -)"; }

mdsh-lang-yaml() { YAML "$(cat)"; }

这效果相当不错,但是,由于 YAML 是一个常量值,为什么不在编译期间将其转换为 JSON 呢?这样,我们可以消除运行时的开销(如果我们保存并重新运行编译后的脚本)

mdsh-compile-yaml() { printf 'JSON %q\n' "$(echo "$1" | yaml2json)"; }

注意这两个函数之间的区别:lang 函数是一个代码 模板mdsh 将其主体复制到您的脚本源中,生成如下所示的代码

{
    YAML "$(cat)"
} <<'```'
... yaml data here ...
​```

compile 函数只是立即运行 yaml2json,然后输出翻译后的数据,如下所示

JSON ...shell-quoted json here...

顺便说一句,注意使用 printf%q 的用法--这会导致数据被正确转义,以便作为命令行参数使用。(当您直接生成代码时,请务必正确转义这些值。当您需要将变量数据插入到生成的代码中时,始终使用带有常量字符串格式的 printf,其中包含 %q 占位符,用于任何独立的参数。)

顺便提一下,compile 函数可以访问实际的块文本,这意味着您可以执行任何类型的代码生成。例如,我可以从 yaml2json 的输出中取出,然后运行 jq,然后遍历输出并编写根据结果设置变量的 bash 代码,或者根据规范生成子命令的代码,或者甚至可能从其中生成一个参数解析器。这些代码生成技术有各种各样的有趣可能性!

编译时变量

除了它们的定位参数之外,编译时钩子如 mdsh-miscmdsh-compile-X 还会接收到一些变量,这些变量在解析特殊块标题或生成错误消息时可能会有帮助。

  • ${tag_words[@]} 是从原始块开头的行中分离的空白分隔的单词数组。例如,如果一个块以 ```foo @bar.baz spam 打开,那么 tag_words=([0]="foo" [1]="@bar.baz" [2]="spam")。(${#tag_words[@]} 是单词的数量。)
  • $mdsh_lang 是 mdsh 看到的块的语言,即 mdsh-lang-X 中的 X。(因此,它是 ${tag_words[0]}${tag_words[1]#@} 或用 _ 替换非标识符字符的整行内容。)
  • 如果正在编译的是文件,则 $MDSH_SOURCE 是源文件名。
  • $block_start 是块在原始源中的起始行号。
  • $mdsh_block 包含块的文本。
  • $mdsh_tag 包含原始块开头的行(即 tag_words 的未拆分形式)。

(这些变量也可以由编译时命令块使用,如下一节所述。)

程序性块生成

mdsh-block 函数允许您以编程方式生成指定语言的代码块。这对于条件块等非常有用。例如,这个 if-env 函数可以在命令块中使用来生成在运行时检查 $WP_ENV 的值的代码,并根据条件执行块的正文。

```shell @mdsh
if-env() {
   printf -v REPLY '|%q' "$@"
   echo "case \$WP_ENV in ${REPLY#|})"
   mdsh-block "$mdsh_lang" "$mdsh_block" "$block_start"
   echo
   echo "esac"
}
```

```css !if-env dev staging
/* This CSS is only used in dev and staging */
```

mdsh-block 函数接受最多四个参数:一个语言、一个块主体、一个起始行号和一个“原始”语言标签(如果未指定,则默认为语言)。前三个参数是可选的,如果省略,则默认为 $mdsh_lang$mdsh_block$block_start。(这意味着上面的代码可以不传递任何参数直接调用 mdsh-block!)

mdsh-block 遵循标准的语言查找逻辑,首先查找 mdsh-lang-X,然后是 mdsh-compile-X,然后回退到 mdsh-misc,如果适用,还会克隆 mdsh-after-X。它不支持命令块或语言别名,因此不能使用 @+!| 表达式。它旨在用于编译时代码,即 ! 命令块、@mdsh 块以及 mdsh-miscmdsh-compile-X 函数等处理器。

命令块和参数

有时您只有一个需要以特定方式处理的块,或者特定语言的每个块在编译或执行时都需要唯一的参数。对于这些场景,您可以定义“命令块”。

命令块是语言标签的第二词以 |+! 开头的代码块。

  • 如果是 |,则语言标签的其余部分在运行时执行,并将块的正文作为标准输入(就像 mdsh-lang-X 函数体一样),并将 shell 变量 mdsh_lang 设置为语言标签的第一词。
  • 如果是 +,则语言标签的其余部分在运行时执行,并将块的正文作为额外的命令行参数,并将 shell 变量 mdsh_lang 设置为语言标签的第一词。
  • 如果是 !,则语言标签的其余部分在 编译 时执行,并将块的正文放在 $1 中,必须将编译后的代码输出到标准输出(就像 mdsh-compile-X 函数一样)。完整的语言标签在 $2 中,代码块的起始行号在 $3 中。所有标准的 编译时变量 都可用,包括 mdsh_langtag_wordsblock_start 以及可能还有 MDSH_SOURCE

在所有上述情况下,$mdsh_lang 被设置为语言标签的第一个单词,但不会被包含在执行的命令行中。(它被假定是一个语法高亮提示,但也可以用作参数,如果你的代码引用了 $mdsh_lang。)

命令块会覆盖正常的语言功能查找,因此不会查找或执行任何 mdsh-after-Xmdsh-lang-Xmdsh-compile-X 函数。因此,以下代码作为 mdsh 的输入

```json !printf "echo %q\n" "# line $3, $mdsh_lang block:"  "def example: $1;"
{"foo": "bar"}
```

```html +echo "The $mdsh_lang is:"
<html />
```

```python |python - "a $mdsh_lang block"
import sys; print "hello, world from "+sys.argv[1]
```

将编译成以下 shell 脚本

echo \#line\ 49, json block:
echo $'def example: {"foo": "bar"}\n;'
echo "The html is:" $'<html />\n'
python - "a python block" <<<'```'
import sys; print "hello, world from "+sys.argv[1]
```

注意,命令块标签的两种形式都可以包含几乎任意的 bash 代码,包括管道、替换等,但 绝对不能 包含反引号字符,因为 Commonmark 规范 要求将这些行视为带有内联代码的普通文本,而不是带围栏的代码块的开始。因此,mdsh 和其他符合 Commonmark 的工具甚至不会将这一行识别为代码块的开始,输入文件的其余部分解析将被打乱,代码将被解释为文本,反之亦然。

技巧和技术

文献测试

使用 cram 功能测试工具测试的 mdsh 创建的文档可以包含示例 shell 会话。只需将 cram 的缩进级别设置为 4,并使用 4 个空格缩进的块来为 cram 测试的示例,可选地用 ~~~ 带围栏代码块包裹,如下所示

    $ echo "hello world!"
    hello world!

Cram 寻找 $> 和一个空格,缩进到一定级别,然后运行命令(s)并验证输出。因此,cram 需要知道你使用的是哪种缩进。

mdsh 忽略 4 个空格缩进和 ~~~ 带围栏块,因此它不会被你的示例弄混淆。你可以通过包含类似 ~~~bash~~~shell 的语言标签来让 github 对你的示例进行语法高亮。

解释如何使用 cram 的所有细节超出了本指南的范围,但在最简单的情况下,使用 cram --indent 4 mydocument.md 将运行 mydocument.md 中的任何 4 个空格缩进的示例。

(注意,cram 实际上并不理解 markdown,因此它将尝试运行任何以 "$ ""> " 开头并在指定缩进处的代码。通常,以这种方式开始的非示例代码可以略微缩进或进一步缩进,以便 cram 忽略它。在 此 PR 合并之前,你将想要使用 此 cram 分支,该分支已被修补以忽略不是示例直接部分的缩进行。如果你使用 .devkit 的 cram 模块,你的测试将自动使用正确版本。)

从生成的脚本中排除块

如果你的脚本包含大量包含带围栏代码块的文档示例,你可能希望排除这些示例的处理或复制到 bash 变量中。你可以通过两种主要方式来实现这一点。

首先,你可以更改指示某些代码块的方式。所有这些目前都被 mdsh 忽略,不会生成任何代码

  • 使用四个空格缩进的代码块,而不是带围栏的

  • 使用 ~~~X 而不是 ```X 带围栏的代码块

  • 使用多于三个反引号或缩进的代码块

  • 没有指定语言的代码块

  • 命令为空或无操作的立即命令块。(即,语言为单个单词后跟一个空格和一个 ! 字符的块,可选地后跟 shell 注释或无操作 shell 命令。)例如,这两个代码块都将从编译后的脚本中省略。

    ​```python ! # mdsh won't do anything with this block
    raise RuntimeError("This won't actually run!")
    ​```
    ​```shell !
    echo "No comment is actually needed.  This block is ignored, too."
    ​```

另外,您可以在 mdsh 块中定义空的 mdsh-compile-X 函数,针对您想排除编译的每种语言,或者定义一个什么也不做的 mdsh-misc 函数。(这将完全禁用数据块;有关 mdsh-misc 的更多信息,请参见下面的元编程和代码生成部分。)

使 Markdown 文件可执行(并可编辑)

如果您想直接运行 markdown 文件,您可以使用 chmod +x 命令并给它一个 shebang 行,例如 #!/usr/bin/env mdsh。这样,您就可以直接运行 somescript.md args..,而无需在前面键入 mdsh

如果您想去掉脚本上的 .md 扩展名,您可能还需要添加一行代码,告诉 Github、您的编辑器等,该文件仍然是 markdown,例如:

#!/usr/bin/env mdsh
<!-- ex: set syntax=markdown : -->
<!-- -*- mode: markdown -*- -->

这将告诉 Github、atom(带有 vim-modeline 包)、vim、emacs 和其他编辑器/显示工具,该文件实际上是 Markdown。

(或者,您可以在文件上保留 .md 扩展名进行编辑,但使用无扩展名的符号链接来运行它,无需键入 .md。)

制作可溯源的脚本(并处理 $0)

通常,编写脚本的方式应使它们定义的函数可供其他脚本使用,通常通过 source 它们。这导致在 bash 脚本末尾编写如下代码的常见模式,以便仅在脚本不是由 source 调用时运行脚本的 main 函数。

if [[ $0 == $BASH_SOURCE ]]; then
    # Not `source`d: run as script
    my-main "$@"
    exit $?
fi

这种做法在 mdsh 中也没有不同:无论您是用 mdsh #! 行执行脚本,还是编译并运行它,变量 $0$BASH_SOURCE 只有在您的程序作为脚本运行时才会相等。

遗憾的是,当 mdsh 通过 #! 行运行时,确保这些值相等的方法是它们都为 。这意味着 $0 将是一个空字符串,这可能会干扰像在“用法”消息中使用它来表示程序名称之类的常见做法。

作为解决方案,mdsh 定义了一个 $MDSH_ZERO 参数,如果您的脚本被直接调用,则定义该参数。它包含如果您的脚本被编译,则 $0$BASH_SOURCE具有的值。您可以使用 ${0:-$MDSH_ZERO} 便携地检索调用脚本名称,无论您的脚本是否编译或解释。

但这仍然不能使您的脚本可 source。毕竟,它是 markdown,而不是 bash。因此,如果另一个脚本尝试 source 它,它将遇到各种语法和其他错误。

为了解决这个问题,我们需要一个 shelldown 标头:两行在 bash 中执行的代码,但在大多数 markdown 渲染中都是隐藏的,并且仍然告诉我们的编辑器(和 Github)忽略 #! 行,并将文件视为 markdown,而不是 bash

#!/usr/bin/env bash
: '
<!-- ex: set ft=markdown : '; eval "$(mdsh --eval "$BASH_SOURCE")" # -->

# My Awesome Script

...code goes here...

现在,我们的脚本使用 bash 语法,足以编译文档并运行结果。使用 mdsh --eval 编译脚本将创建一个安全版本的脚本,可以 evalsource,通过在代码末尾添加一个执行 return $?exit $? 的额外行,具体取决于脚本是否被 source。(这确保了 bash 在处理文件时停止处理,在它能够被随后的 markdown 内容弄混淆之前。)

因此,该文件可以无问题地运行或 source。更重要的是,您可以使用 mdsh --compile 它创建一个纯 bash 脚本(仍然可运行和 source),无需进行任何代码更改。

这是因为当您直接运行或 source 脚本时,您实际上执行的是将编译的 相同 代码,在相同的环境中,$BASH_SOURCE 指向实际文件。(并且 $0 与它匹配,除非它正在被 source。)

为什么不用这种方法呢?当然可以。如果你不介意把它复制粘贴到所有新的脚本中,那就按你的意愿去做吧!然而,对于一次性的脚本,其中没有使用到$0的值,你也不关心编辑器支持,使用mdsh#!行确实是一个更简单的方法。

语法高亮和语言别名

由于mdsh不是广为人知的语言,GitHub和其他Markdown编辑/处理工具通常不知道如何正确地突出显示它们。作为一种权宜之计,你可以给一个shell @mdsh的块标签,大多数工具会将其解释为用于突出显示的shell块。例如:

```shell @mdsh
echo 'echo "Most tools will highlight this block as shell script"'
```

元编程和代码生成

mdsh标记的块中产生的任何输出都将成为在块出现的位置生成的bash脚本的一部分。这意味着你可以简单地使用cat其他bash文件来包含它们(或使用mdsh-embed;见下一节),或者在那里生成任何其他你想要的代码。这可以是一个有用的替代方案,代替使用source来加载函数,因为它意味着生成的脚本可以被--compile到一个不包含其他模块的单个文件中。

如果你要以某种方式编程处理单个块(例如,从它们的语言标签中提取文件名),你可以定义一个mdsh-misc函数。对于每个没有mdsh-lang-Xmdsh-compile-X函数的块,mdsh-misc会使用语言标签和块内容作为参数被调用,并且它的输出会被附加到文件中的那个位置编译的脚本上。

所以,例如,当运行此脚本时,它将文本块的输出内容保存到file1.txt中。

​```mdsh
mdsh-misc() {
    if [[ $1 == *'>'* ]]; then echo -n "$2" >"${1#*>}"; fi
}
​```

​```text >file1.txt
Some text goes here!
​```

当然,mdsh-misc的潜在应用远远不止是将块写入文件。例如,你可以

  • 模拟其他文献编程工具的“编织”功能(通过让mdsh-misc根据块的标签信息将块内容保存到不同的变量中,然后在程序结束时使用一个输出保存的块的mdsh块)
  • 将标签解释为处理块的参数
  • 将以|开头的块标签视为预处理块的管道

...以及你能够想象到的任何其他事情。

"静态链接" 用于分发

你可以使用mdsh-embed函数将其他bash模块的源代码嵌入到你的脚本中。在mdsh块内部调用mdsh-embed modulename将会在PATH中搜索modulename(除非modulename包含一个/),然后输出其源代码,并用heredoc和source命令包裹起来。(这确保了嵌入的模块知道它被加载了,而不是通过命令行运行,即使嵌入脚本通过命令行运行的。)

结果是,通过在mdsh块中使用mdsh-embed来加载你的模块(而不是在shell块中使用source),你可以将你的脚本--compile到一个“静态链接的可执行文件”。也就是说,你可以创建一个包含所有所需模块的单个文件,这样你的用户就不需要自己安装所有依赖项,也不需要特定的包管理器来安装你的脚本。

扩展 mdsh 或重用其函数

从bash脚本中源mdsh将定义所有其函数,但不会实际运行一个程序。这允许你更改命令行参数的处理方式,或预先定义额外的语言钩子、拆卸钩子等。(你也可以这样做,以便使用内建的Markdown处理函数。)

(注意,源mdsh将设置bash到“非官方严格模式”,即-euo pipefailmdsh假设这些设置是有效的,所以更改它们可能会有不良后果。)

如果你编写的是“带有更多语言的mdsh”,你可以这样做

#!/usr/bin/env bash
source "$(command -v mdsh)"

mdsh-compile-somelang() {
    # etc.
}

# ...

[[ $0 == $BASH_SOURCE ]] && mdsh-main "$@"

也就是说,只需源码mdsh并定义您的附加语言处理器,然后运行mdsh-main "$@":您的脚本将具有与mdsh相同的命令行界面,但所有帮助信息都将引用您的脚本名称,而不是mdsh

添加文件头或尾

如果您的mdsh扩展版本需要向生成的文件添加标题或页脚,您可以定义名为mdsh:file-header和/或mdsh:file-footer的函数。--compile选项将在编译过程的开始和结束时调用这些函数一次,将整个输出封装起来。--eval的行为类似,但--eval页脚将出现在mdsh:file-footer之后。

修改现有函数

在某些情况下,您可能还希望通过替换一些函数来改变mdsh的部分行为。但是,您可以通过使用mdsh-rewrite来避免将那些函数复制到您的代码中。mdsh-rewrite是一个通常在mdsh编译器中用于重写mdsh-lang-Xmdsh-after-X函数体的函数,但您可以将它改编为类似AOP的bash函数编辑。

例如,jqmd通过在mdsh.--compile函数的开始和结束处添加函数调用,向使用jqmd --compilejqmd --eval编译的文件添加标题和页脚。

eval "mdsh.--compile() $(mdsh-rewrite mdsh.--compile '{ jqmd-header;' 'jqmd-footer; }')"

(mdsh-rewrite接受一个函数名称和两个可选字符串,这两个字符串将替换函数体中开头和结尾的花括号行。结果输出到stdout,成为新函数的主体。)

可用函数

以下函数可用于在源码mdsh的脚本中进行使用或修改

  • mdsh-run mdfile [cache-key [args...]] -- 以args作为其位置参数($1$2等)的编译后的指定markdown文件source。如果存在,将使用$MDSH_CACHE中的目录来缓存编译版本,文件名使用cache-key生成。如果cache-key缺失或为空,则使用mdfile作为缓存键。

    要设置特定的缓存目录或禁用缓存,请使用与mdsh-run相同的行设置它,例如,使用MDSH_CACHE= mdsh-run somefile来运行somefile而不缓存。源码或运行mdsh将默认的MDSH_CACHE设置为$XDG_CACHE_HOME/mdsh$HOME/.cache/mdsh,具体取决于您的操作系统。

  • mdsh-use-cache [cachedir] -- 将MDSH_CACHE设置为cachedir。如果没有提供任何参数,则将MDSH_CACHE重置为默认值$XDG_CACHE_HOME/mdsh$HOME/.cache/mdsh,具体取决于您的操作系统。

  • run-markdown mdfile args... -- 以args作为其位置参数($1$2等)source编译后的mdfile,不使用缓存。

  • mdsh-error format args... -- 将format args打印到stderr并以错误级别64(EX_USAGE)终止进程。(自动添加换行符到格式字符串。)

  • mdsh-compile -- 接受stdin中的markdown,并在stdout输出bash代码。编译在子shell中执行,因此正在编译的代码中定义的钩子函数不会影响调用者的环境。但是,已经定义在调用者环境中的钩子函数将被用于翻译相关语言块。

  • mdsh-source -- 与mdsh-compile类似,但不运行在子shell中,因此任何钩子函数或编译时变量更改将影响调用者,就像包含的源码是包含文档的一部分。

  • mdsh-embed modulename -- 在PATH上查找modulename(除非它包含一个/),并将它的内容包装在source命令和heredoc中。如果找不到modulename或无法读取它,则返回失败。(注意:与Bash的source命令不同,此函数不会回退到在当前目录中查找模块。如果您想在当前目录中查找文件,请使用./modulename。)

  • mdsh-rewrite 函数 之前 之后 -- 将函数的正文块输出到标准输出,可选地使用之前之后替换开闭花括号。 (如果您使用此命令“编辑”函数,请记住替换必须包括开闭花括号,并且闭花括号之前必须是一个换行符、分号或空格。)

  • mdsh-make 源文件 目标文件 [命令 参数...] -- 如果目标文件不存在或其时间戳与源文件不同,则将源文件编译为目标文件,在开始编译之前运行命令 参数...。编译(和命令 参数...)在子shell中运行。编译成功后,会更新目标文件,使其时间戳与源文件相同。

  • mdsh-cache 缓存目录 源文件 [键 [命令 参数...]] -- 与mdsh-make类似,但目标文件作为缓存目录内的文件名自动生成,并在$REPLY中返回。如果指定了非空,则使用而不是源文件来生成目标文件名。如果缓存目录不存在,则会自动创建。生成的文件名是源文件的转义版本,这样它们始终位于缓存目录下,即使源文件名包含斜杠也是如此。

  • exit [代码 [消息 [参数...]]] -- 在显示消息到标准错误后,以代码状态退出(如果没有指定,则默认为$?),如果提供了参数,则使用消息作为printf格式字符串进行格式化(自动添加换行符)。