Skip to content

Commit 030bbed

Browse files
authored
Merge pull request #14 from lempiji/feat/support-lang-sh
Support execution of shell and bash code blocks
2 parents 6103d0e + 5efc2b3 commit 030bbed

6 files changed

Lines changed: 461 additions & 119 deletions

File tree

.github/workflows/test-md.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,17 @@ jobs:
4141
dub run md --compiler=$DC -- examples/test-compiler.md --compiler=$DC
4242
dub run md --compiler=$DC -- examples/test-build.md --build=release --compiler=$DC
4343
dub run md --compiler=$DC -- examples/test-compiler.md --build=release --compiler=$DC
44+
dub run md --compiler=$DC -- examples/test-shell.md
4445
4546
- name: 'Run filter tests'
4647
run: dub run md -- examples/test-filter.md --filter test1
4748
- name: 'Run multiple filters tests'
4849
run: dub run md -- examples/test-filter-multiple.md --filter test1 --filter test2
4950
- name: 'Run no matching filter tests'
5051
run: dub run md -- examples/test-filter-multiple.md --filter test100
52+
- name: 'Run shell filters tests'
53+
run: |
54+
dub run md -- examples/test-shell.md --filter sh_test
55+
dub run md -- examples/test-shell.md --filter bash_test
56+
- name: 'Oracle log format tests'
57+
run: bash examples/oracle-log-format.sh

README.ja.md

Lines changed: 83 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ dub run md -- --help
2525

2626
## 機能概要
2727

28-
言語に `d` または `D` と指定されているコードブロックが実行されます。
28+
言語に `d`, `D`, `sh`, `bash` と指定されているコードブロックが実行されます。
2929

3030
### ブロックの結合
3131

@@ -101,41 +101,79 @@ auto message = "filter test";
101101
writeln(message);
102102
```
103103

104-
```d name=filter_test2
105-
import std;
106-
107-
auto message2 = "another filter test";
108-
writeln(message2);
109-
```
110-
111-
#### 複数の名前指定
112-
113-
1つのコードブロックに対して、複数の `name` 属性を指定できます。
114-
このブロックは、指定したすべての名前のブロックに取り込まれます。
115-
116-
~~~
117-
```d name=multi_name_test1 name=multi_name_test2
118-
```
119-
~~~
120-
121-
```
122-
dub run md -- README.md --filter=multi_name_test1
123-
dub run md -- README.md --filter=multi_name_test2
124-
```
125-
126-
```d name=multi_name_test1 name=multi_name_test2
127-
import std.stdio;
128-
```
129-
130-
```d name=multi_name_test1
131-
writeln("multi-name test1");
132-
```
133-
134-
```d name=multi_name_test2
135-
writeln("multi-name test2");
136-
```
137-
138-
### 独立実行
104+
```d name=filter_test2
105+
import std;
106+
107+
auto message2 = "another filter test";
108+
writeln(message2);
109+
```
110+
111+
#### 複数の名前指定
112+
113+
1つのコードブロックに対して、複数の `name` 属性を指定できます。
114+
このブロックは、指定したすべての名前のブロックに取り込まれます。
115+
116+
~~~
117+
```d name=multi_name_test1 name=multi_name_test2
118+
```
119+
~~~
120+
121+
```
122+
dub run md -- README.md --filter=multi_name_test1
123+
dub run md -- README.md --filter=multi_name_test2
124+
```
125+
126+
```d name=multi_name_test1 name=multi_name_test2
127+
import std.stdio;
128+
```
129+
130+
```d name=multi_name_test1
131+
writeln("multi-name test1");
132+
```
133+
134+
```d name=multi_name_test2
135+
writeln("multi-name test2");
136+
```
137+
138+
### シェルブロック
139+
140+
`sh``bash` のブロックも、Dと同じ `name``--filter` のルールで実行されます。
141+
`name` 省略時は `main` 扱いで、1つのブロックに複数の `name` も指定できます。
142+
143+
~~~
144+
```sh name=shell_sample
145+
```
146+
~~~
147+
148+
```
149+
dub run md -- README.md --filter=shell_sample
150+
dub run md -- README.md --filter=bash_sample
151+
```
152+
153+
```sh name=shell_sample
154+
shared_shell="from-sh"
155+
echo "sh:${shared_shell}"
156+
```
157+
158+
```bash name=shell_sample name=bash_sample
159+
shared_bash="from-bash"
160+
```
161+
162+
```bash name=bash_sample
163+
parts=("A" "B")
164+
echo "bash:${shared_bash}:${parts[1]}"
165+
```
166+
167+
実行方式:
168+
1. `sh` ブロックは `sh -eu <temp_script_path>` で実行
169+
2. `bash` ブロックは `bash -eu -o pipefail <temp_script_path>` で実行
170+
3. 実行時のカレントディレクトリは `md` コマンドを起動したディレクトリ
171+
4. 終了コードが0以外なら失敗扱い
172+
5. `--build`, `--compiler`, `--arch`, `--dependency`, `--dubsdl` はD実行にのみ適用
173+
6. `--buildOnly` 指定時はシェルスクリプト実行をスキップ
174+
7. `--show-lang` を指定すると begin/end ラベルを `<language>:<block-name>` 形式で表示(既定は従来形式)
175+
176+
### 独立実行
139177

140178
1つのコードブロックを他のブロックと結合せず、独立して実行させるためには `single` という属性を付与します。
141179

@@ -219,7 +257,14 @@ void main()
219257

220258
### 実行時の仕組み
221259

222-
tempディレクトリに `.md` ディレクトリを作り、dubのシングルファイル形式のソースを生成、 `dub run --single md_xxx.md` といったコマンドで実行します。
260+
tempディレクトリに `.md` ディレクトリを作り、実行単位ごとの一時ファイルを生成して実行します。
261+
262+
言語ごとの実行方式:
263+
1. D: dubのシングルファイル形式ソースを生成し、 `dub run --single md_xxx.d` で実行(`--buildOnly` 時は `dub build --single ...`
264+
2. sh: スクリプトを生成し、 `sh -eu md_xxx.sh` で実行
265+
3. bash: スクリプトを生成し、 `bash -eu -o pipefail md_xxx.sh` で実行
266+
267+
名前付きブロックは `language + name` 単位で結合され、 `--filter` 指定時は対象nameのみ実行されます。
223268

224269
また、既定のパッケージ参照を実現するため、ソースの先頭に以下のようなコメントを自動的に付与します。
225270

@@ -279,4 +324,4 @@ void main()
279324
auto arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
280325
auto result = arr.sum();
281326
}
282-
```
327+
```

README.md

Lines changed: 84 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ dub run md -- --help
2525

2626
## Features
2727

28-
The code block whose language is specified as `d` or `D` will be executed.
28+
The code block whose language is specified as `d`, `D`, `sh`, or `bash` will be executed.
2929

3030
### Combine blocks
3131

@@ -99,43 +99,81 @@ auto message = "filter test";
9999
writeln(message);
100100
```
101101

102-
```d name=filter_test2
103-
import std;
104-
105-
auto message2 = "another filter test";
106-
writeln(message2);
107-
```
108-
109-
#### Multiple names
110-
111-
You can assign multiple `name` attributes to a single code block.
112-
That block will be included in every matching named block.
113-
114-
~~~
115-
```d name=multi_name_test1 name=multi_name_test2
116-
```
117-
~~~
118-
119-
```
120-
dub run md -- README.md --filter=multi_name_test1
121-
dub run md -- README.md --filter=multi_name_test2
122-
```
123-
124-
```d name=multi_name_test1 name=multi_name_test2
125-
import std.stdio;
126-
```
127-
128-
```d name=multi_name_test1
129-
writeln("multi-name test1");
130-
```
131-
132-
```d name=multi_name_test2
133-
writeln("multi-name test2");
134-
```
135-
136-
137-
138-
### Scoped block
102+
```d name=filter_test2
103+
import std;
104+
105+
auto message2 = "another filter test";
106+
writeln(message2);
107+
```
108+
109+
#### Multiple names
110+
111+
You can assign multiple `name` attributes to a single code block.
112+
That block will be included in every matching named block.
113+
114+
~~~
115+
```d name=multi_name_test1 name=multi_name_test2
116+
```
117+
~~~
118+
119+
```
120+
dub run md -- README.md --filter=multi_name_test1
121+
dub run md -- README.md --filter=multi_name_test2
122+
```
123+
124+
```d name=multi_name_test1 name=multi_name_test2
125+
import std.stdio;
126+
```
127+
128+
```d name=multi_name_test1
129+
writeln("multi-name test1");
130+
```
131+
132+
```d name=multi_name_test2
133+
writeln("multi-name test2");
134+
```
135+
136+
### Shell blocks
137+
138+
`sh` and `bash` blocks are executed with the same `name` and `--filter` rules as D blocks.
139+
The default name is `main`, and a single block can have multiple names.
140+
141+
~~~
142+
```sh name=shell_sample
143+
```
144+
~~~
145+
146+
```
147+
dub run md -- README.md --filter=shell_sample
148+
dub run md -- README.md --filter=bash_sample
149+
```
150+
151+
```sh name=shell_sample
152+
shared_shell="from-sh"
153+
echo "sh:${shared_shell}"
154+
```
155+
156+
```bash name=shell_sample name=bash_sample
157+
shared_bash="from-bash"
158+
```
159+
160+
```bash name=bash_sample
161+
parts=("A" "B")
162+
echo "bash:${shared_bash}:${parts[1]}"
163+
```
164+
165+
Execution details:
166+
1. `sh` blocks run as `sh -eu <temp_script_path>`
167+
2. `bash` blocks run as `bash -eu -o pipefail <temp_script_path>`
168+
3. Scripts are executed from the current working directory where `md` is launched
169+
4. Non-zero exit code is treated as an error
170+
5. `--build`, `--compiler`, `--arch`, `--dependency`, and `--dubsdl` apply only to D execution
171+
6. `--buildOnly` skips shell script execution
172+
7. `--show-lang` prints language-aware begin/end labels as `<language>:<block-name>` (default keeps the original label format)
173+
174+
175+
176+
### Scoped block
139177

140178
To make a single block of code run independently without being combined with other blocks, give it the attribute `single`.
141179

@@ -218,7 +256,14 @@ void main()
218256

219257
### How it works
220258

221-
Create a `.md` directory in the temp directory, generate the source in dub single file format, and run it with a command like `dub run --single md_xxx.md`.
259+
Create a `.md` directory in the temp directory and generate temporary files for each execution unit.
260+
261+
Execution model by language:
262+
1. D: generate a dub single-file source and run it with `dub run --single md_xxx.d` (or `dub build --single ...` with `--buildOnly`)
263+
2. sh: generate a script file and run `sh -eu md_xxx.sh`
264+
3. bash: generate a script file and run `bash -eu -o pipefail md_xxx.sh`
265+
266+
Named blocks are combined by `language + name` before execution, and `--filter` narrows the target names.
222267

223268
It also automatically adds the following comment to the beginning of the source to achieve default package references.
224269

examples/oracle-log-format.sh

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5+
cd "$ROOT_DIR"
6+
7+
run_md() {
8+
HOME=/tmp dub run -q -- "$@"
9+
}
10+
11+
assert_has() {
12+
local output="$1"
13+
local pattern="$2"
14+
local message="$3"
15+
if ! grep -Eq "$pattern" <<<"$output"; then
16+
echo "[oracle] FAIL: $message" >&2
17+
echo "[oracle] missing pattern: $pattern" >&2
18+
exit 1
19+
fi
20+
}
21+
22+
assert_not_has() {
23+
local output="$1"
24+
local pattern="$2"
25+
local message="$3"
26+
if grep -Eq "$pattern" <<<"$output"; then
27+
echo "[oracle] FAIL: $message" >&2
28+
echo "[oracle] unexpected pattern: $pattern" >&2
29+
exit 1
30+
fi
31+
}
32+
33+
echo "[oracle] check default named log format"
34+
out_default_shell="$(run_md examples/test-shell.md 2>&1)"
35+
assert_has "$out_default_shell" '^begin: sh_test$' 'default log must keep name-only format for sh_test'
36+
assert_has "$out_default_shell" '^begin: bash_test$' 'default log must keep name-only format for bash_test'
37+
assert_not_has "$out_default_shell" '^begin: (d|sh|bash):' 'default log must not include language prefix for named blocks'
38+
39+
echo "[oracle] check --show-lang named log format"
40+
out_lang_shell="$(run_md examples/test-shell.md --show-lang 2>&1)"
41+
assert_has "$out_lang_shell" '^begin: sh:sh_test$' '--show-lang must include sh:sh_test'
42+
assert_has "$out_lang_shell" '^begin: sh:bash_test$' '--show-lang must include sh:bash_test'
43+
assert_has "$out_lang_shell" '^begin: bash:sh_test$' '--show-lang must include bash:sh_test'
44+
assert_has "$out_lang_shell" '^begin: bash:bash_test$' '--show-lang must include bash:bash_test'
45+
46+
echo "[oracle] check default single/global log format"
47+
out_default_readme="$(run_md README.md 2>&1)"
48+
assert_has "$out_default_readme" '^begin single: 0$' 'default single log must keep numeric label'
49+
assert_has "$out_default_readme" '^begin global :0$' 'default global log must keep numeric label (0)'
50+
assert_has "$out_default_readme" '^begin global :1$' 'default global log must keep numeric label (1)'
51+
assert_not_has "$out_default_readme" '^begin single: d:0$' 'default single log must not include language prefix'
52+
assert_not_has "$out_default_readme" '^begin global :d:0$' 'default global log must not include language prefix'
53+
54+
echo "[oracle] check --show-lang single/global log format"
55+
out_lang_readme="$(run_md README.md --show-lang 2>&1)"
56+
assert_has "$out_lang_readme" '^begin: d:main$' '--show-lang must include d prefix for named D block'
57+
assert_has "$out_lang_readme" '^begin: sh:shell_sample$' '--show-lang must include sh prefix for named shell block'
58+
assert_has "$out_lang_readme" '^begin single: d:0$' '--show-lang must include d prefix for single block'
59+
assert_has "$out_lang_readme" '^begin global :d:0$' '--show-lang must include d prefix for global block (0)'
60+
assert_has "$out_lang_readme" '^begin global :d:1$' '--show-lang must include d prefix for global block (1)'
61+
62+
echo "[oracle] PASS"

0 commit comments

Comments
 (0)