rundoc 6.0.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +89 -0
- data/lib/rundoc/code_command/bash.rb +7 -2
- data/lib/rundoc/code_command/rundoc/ensure_later.rb +1 -1
- data/lib/rundoc/version.rb +1 -1
- data/test/integration/ensure_later_test.rb +30 -0
- data/test/rundoc/code_commands/bash_test.rb +24 -0
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 30dcb03fc239fe1ddd02618341f780c8158ec145646049df7b597206dd7692db
|
|
4
|
+
data.tar.gz: c31c17bfa5997dc0c79b218e42493d719f6f19d26c1bc758f7d17f24bbe8d996
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d1c17df9586e3c8d767f8fa79c3d8afebddf5a84c09ae6cbdee14286bb184ee7d51c0ada0827a4dd8a0d480266b63bc1713872200d841b5ae0d5a0af482c2f20
|
|
7
|
+
data.tar.gz: a3e244880fe064cb86360953f8b3ca4e6b3569201f9bc0c96287686dd99a17a4c59cb3122160a4024b37808e98265758dd40290643cdbb84b0f5826fce5e234c
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
## HEAD
|
|
2
2
|
|
|
3
|
+
## 7.0.0
|
|
4
|
+
|
|
5
|
+
- Changed: Shell commands now run under `bash -eo pipefail` instead of `/bin/sh`. This surfaces failures in pipelines and compound commands that were previously swallowed. See the README for details on SIGPIPE interactions.
|
|
6
|
+
|
|
3
7
|
## 6.0.0
|
|
4
8
|
|
|
5
9
|
- Added: `rundoc.ensure_later(dir: :cwd)` command to register cleanup blocks that run on every build (success and failure). Useful for guaranteed resource teardown (e.g., destroying Heroku apps). Multiple blocks execute in order; failures in one block do not stop the rest.
|
data/README.md
CHANGED
|
@@ -259,6 +259,8 @@ However this command would fall on its face:
|
|
|
259
259
|
|
|
260
260
|
These custom commands are kept to a minimum, and for the most part behave as you would expect them to. Write your docs as you normally would and check the output frequently.
|
|
261
261
|
|
|
262
|
+
Shell commands run under `bash -eo pipefail` to catch silent failures in pipelines and compound commands. See [Bash Error Handling](#bash-error-handling) for details and SIGPIPE caveats.
|
|
263
|
+
|
|
262
264
|
Running shell commands like this can be very powerful, you'll likely want more control of how you manipulate files in your project. To do this you can use the `file.` namespace:
|
|
263
265
|
|
|
264
266
|
## Dynamic command templating
|
|
@@ -719,6 +721,93 @@ The output of this will not be included in the document. Multiple ensure blocks
|
|
|
719
721
|
|
|
720
722
|
If a build is successful, but an `ensure_later` block fails, the build will be considered a failure. A failure in one block will not stop the rest from executing.
|
|
721
723
|
|
|
724
|
+
## Bash Error Handling
|
|
725
|
+
|
|
726
|
+
### Bash `set -eo pipefail` explained
|
|
727
|
+
|
|
728
|
+
Bash error behavior is configurable. Rundoc uses `set -eo pipefail` to catch common problems not caught by the default error mode.
|
|
729
|
+
|
|
730
|
+
If a failing command is piped to a valid command, by default bash will "hide" the failure by returning a zero (success) exit code:
|
|
731
|
+
|
|
732
|
+
```term
|
|
733
|
+
$ bash -c "cat does-not-exist | head -n1"
|
|
734
|
+
cat: does-not-exist: No such file or directory
|
|
735
|
+
$ echo $?
|
|
736
|
+
0
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
With `-o pipefail`, the pipeline reports the exit status of the last command that failed rather than the last command in the pipe. With `-e`, bash exits immediately when a command fails. Together, `-eo pipefail` ensures a failing command's exit status is not hidden. Here, the exit code `1` comes from `cat` failing to open a file that doesn't exist:
|
|
740
|
+
|
|
741
|
+
```term
|
|
742
|
+
$ bash -eo pipefail -c "cat does-not-exist | head -n1"
|
|
743
|
+
cat: does-not-exist: No such file or directory
|
|
744
|
+
$ echo $?
|
|
745
|
+
1
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
A similar scenario is when multiple commands are given on one line separated by a semicolon. Without `-e`, bash continues past the failure:
|
|
749
|
+
|
|
750
|
+
```term
|
|
751
|
+
$ bash -c "cat does-not-exist; echo 'done'"
|
|
752
|
+
cat: does-not-exist: No such file or directory
|
|
753
|
+
done
|
|
754
|
+
$ echo $?
|
|
755
|
+
0
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
With `-e`, bash exits immediately after the failing command:
|
|
759
|
+
|
|
760
|
+
```term
|
|
761
|
+
$ bash -eo pipefail -c "cat does-not-exist; echo 'done'"
|
|
762
|
+
cat: does-not-exist: No such file or directory
|
|
763
|
+
$ echo $?
|
|
764
|
+
1
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
These settings help prevent silent failures from slipping through your Rundoc scripts.
|
|
768
|
+
|
|
769
|
+
### Bash SIGPIPE
|
|
770
|
+
|
|
771
|
+
Tools like `head -n1` and `grep -m1 "value"` read only part of their input and then exit. When the upstream process next tries to write to the now-closed pipe, the kernel delivers a `SIGPIPE` signal to terminate it. This behavior is useful when the input is a never-ending stream but you only need a subset:
|
|
772
|
+
|
|
773
|
+
```term
|
|
774
|
+
$ bash -c 'yes "output" | head -n1'
|
|
775
|
+
output
|
|
776
|
+
$ echo $?
|
|
777
|
+
0
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
However, this behavior negatively interacts with `set -eo pipefail`. Even though `head -n1` successfully produced its output and the command appears to run cleanly, `pipefail` reports the upstream process's SIGPIPE termination as a non-zero exit:
|
|
781
|
+
|
|
782
|
+
```term
|
|
783
|
+
$ bash -eo pipefail -c 'yes "output" | head -n1'
|
|
784
|
+
output
|
|
785
|
+
$ echo $?
|
|
786
|
+
141
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
If the input is fully buffered (such as reading from a small file), `head` can finish before the writer needs to write again, so SIGPIPE is never triggered. That means `cat small-file.txt | head -n1` is reasonably safe, but this behavior can surface with larger files:
|
|
790
|
+
|
|
791
|
+
```term
|
|
792
|
+
$ tmpfile=$(mktemp)
|
|
793
|
+
$ seq 1 200000 > "$tmpfile"
|
|
794
|
+
$ bash -eo pipefail -c "cat $tmpfile | head -n1"
|
|
795
|
+
1
|
|
796
|
+
$ echo $?
|
|
797
|
+
141
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
This SIGPIPE behavior can cause your rundoc script to exit early when you aren't expecting it.
|
|
801
|
+
|
|
802
|
+
You can rewrite some commands to take a file directly. For example, `cat Gemfile | head -n 5` can be rewritten as `head -n 5 Gemfile` without SIGPIPE risk.
|
|
803
|
+
|
|
804
|
+
Here are common commands that can trigger a SIGPIPE:
|
|
805
|
+
|
|
806
|
+
- `head` with `-n <N>` or `-c <N>`
|
|
807
|
+
- `grep` with `-m <N>` (max count)
|
|
808
|
+
- `sed <N>q` (quit after N lines)
|
|
809
|
+
- `awk` with `{exit}` — e.g., rewrite `awk '/^Start/ {flag=1} /^End/ {exit} flag'` without exit as `awk '/^Start/ {flag=1} /^End/ {flag=0} flag'`
|
|
810
|
+
|
|
722
811
|
## Writing a new command
|
|
723
812
|
|
|
724
813
|
> Note: This is an advanced topic and this interface is unstable.
|
|
@@ -45,13 +45,18 @@ class Rundoc::CodeCommand::BashRunner
|
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def shell(cmd, stdin = nil)
|
|
48
|
-
cmd = "(#{cmd}) 2>&1"
|
|
49
48
|
msg = "Running: $ '#{cmd}'"
|
|
50
49
|
msg << " with stdin: '#{stdin.inspect}'" if stdin && !stdin.empty?
|
|
51
50
|
io.puts msg
|
|
52
51
|
|
|
53
52
|
result = +""
|
|
54
|
-
|
|
53
|
+
# -e: Abort on first failure in compound commands (e.g. `bundle install; echo done`)
|
|
54
|
+
# so the exit status reflects the real failure, not a trailing success.
|
|
55
|
+
# -o pipefail: Report the first failure in a pipeline (e.g. `git push | tee log`)
|
|
56
|
+
# instead of only the last command's exit status.
|
|
57
|
+
# -u (nounset) is intentionally omitted: tutorial commands routinely
|
|
58
|
+
# reference environment variables that may not be set.
|
|
59
|
+
IO.popen(["bash", "-eo", "pipefail", "-c", cmd, err: [:child, :out]], "w+") do |pipe|
|
|
55
60
|
pipe << stdin if stdin
|
|
56
61
|
pipe.close_write
|
|
57
62
|
|
data/lib/rundoc/version.rb
CHANGED
|
@@ -332,4 +332,34 @@ class IntegrationEnsureLaterTest < Minitest::Test
|
|
|
332
332
|
end
|
|
333
333
|
end
|
|
334
334
|
end
|
|
335
|
+
|
|
336
|
+
def test_log_output_shows_dir_mode_name
|
|
337
|
+
Dir.mktmpdir do |dir|
|
|
338
|
+
Dir.chdir(dir) do
|
|
339
|
+
dir = Pathname(dir)
|
|
340
|
+
|
|
341
|
+
source_path = dir.join("RUNDOC.md")
|
|
342
|
+
source_path.write <<~EOF
|
|
343
|
+
```
|
|
344
|
+
:::-- rundoc.ensure_later(dir: :cwd)
|
|
345
|
+
puts "hello"
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
```
|
|
349
|
+
:::>> $ echo "hello"
|
|
350
|
+
```
|
|
351
|
+
EOF
|
|
352
|
+
|
|
353
|
+
io = StringIO.new
|
|
354
|
+
Rundoc::CLI.new(
|
|
355
|
+
io: io,
|
|
356
|
+
source_path: source_path,
|
|
357
|
+
on_success_dir: dir.join(SUCCESS_DIRNAME)
|
|
358
|
+
).call
|
|
359
|
+
|
|
360
|
+
output = io.string
|
|
361
|
+
assert_includes output, "Registering ensure_later block (dir: cwd => /"
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
end
|
|
335
365
|
end
|
|
@@ -25,6 +25,30 @@ class BashTest < Minitest::Test
|
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
def test_pipefail_catches_early_failure
|
|
29
|
+
command = "false | true"
|
|
30
|
+
bash = Rundoc::CodeCommand::BashRunner.new(
|
|
31
|
+
render_command: false,
|
|
32
|
+
render_result: false,
|
|
33
|
+
io: StringIO.new,
|
|
34
|
+
user_args: Rundoc::CodeCommand::BashArgs.new(command)
|
|
35
|
+
)
|
|
36
|
+
error = assert_raises(RuntimeError) { bash.call }
|
|
37
|
+
assert_match(/exited with non zero status/, error.message)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_errexit_catches_compound_command_failure
|
|
41
|
+
command = "false; echo done"
|
|
42
|
+
bash = Rundoc::CodeCommand::BashRunner.new(
|
|
43
|
+
render_command: false,
|
|
44
|
+
render_result: false,
|
|
45
|
+
io: StringIO.new,
|
|
46
|
+
user_args: Rundoc::CodeCommand::BashArgs.new(command)
|
|
47
|
+
)
|
|
48
|
+
error = assert_raises(RuntimeError) { bash.call }
|
|
49
|
+
assert_match(/exited with non zero status/, error.message)
|
|
50
|
+
end
|
|
51
|
+
|
|
28
52
|
def test_stdin
|
|
29
53
|
command = "tail -n 2"
|
|
30
54
|
bash = Rundoc::CodeCommand::BashRunner.new(
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rundoc
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 7.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Richard Schneeman
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-06-05 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: thor
|
|
@@ -304,6 +305,7 @@ homepage: https://github.com/schneems/rundoc
|
|
|
304
305
|
licenses:
|
|
305
306
|
- MIT
|
|
306
307
|
metadata: {}
|
|
308
|
+
post_install_message:
|
|
307
309
|
rdoc_options: []
|
|
308
310
|
require_paths:
|
|
309
311
|
- lib
|
|
@@ -318,7 +320,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
318
320
|
- !ruby/object:Gem::Version
|
|
319
321
|
version: '0'
|
|
320
322
|
requirements: []
|
|
321
|
-
rubygems_version: 4.
|
|
323
|
+
rubygems_version: 3.4.19
|
|
324
|
+
signing_key:
|
|
322
325
|
specification_version: 4
|
|
323
326
|
summary: RunDOC generates runable code from docs
|
|
324
327
|
test_files: []
|