rundoc 5.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/.github/workflows/ci.yml +1 -0
- data/CHANGELOG.md +9 -0
- data/README.md +151 -1
- data/lib/rundoc/cli.rb +14 -1
- data/lib/rundoc/code_command/bash/cd.rb +5 -11
- data/lib/rundoc/code_command/bash.rb +7 -2
- data/lib/rundoc/code_command/empty_binding.rb +18 -0
- data/lib/rundoc/code_command/print/erb.rb +1 -14
- data/lib/rundoc/code_command/rundoc/ensure_later.rb +59 -0
- data/lib/rundoc/code_command/rundoc_command.rb +5 -1
- data/lib/rundoc/code_command.rb +1 -0
- data/lib/rundoc/peg_parser.rb +8 -0
- data/lib/rundoc/version.rb +1 -1
- data/lib/rundoc.rb +37 -0
- data/rundoc.gemspec +1 -0
- data/test/integration/ensure_later_test.rb +365 -0
- data/test/integration/print_test.rb +51 -0
- data/test/rundoc/code_commands/bash_test.rb +24 -0
- data/test/rundoc/peg_parser_test.rb +25 -0
- metadata +23 -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/.github/workflows/ci.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
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
|
+
|
|
7
|
+
## 6.0.0
|
|
8
|
+
|
|
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.
|
|
10
|
+
- Changed: The ruby context in `rundoc` and `rundoc.configure` commands is now the same binding as the default ERB evaluation. This means that methods can be defined in one and used in both.
|
|
11
|
+
|
|
3
12
|
## 5.0.0
|
|
4
13
|
|
|
5
14
|
- Added comment syntax. Use an octothorpe (`#`) after the visibility markers to comment out any commands and make them a no-op.
|
data/README.md
CHANGED
|
@@ -98,6 +98,8 @@ This will generate a project folder with your project in it, and a markdown `REA
|
|
|
98
98
|
- [website.screenshot](#screenshots)
|
|
99
99
|
- Configure RunDOC
|
|
100
100
|
- [rundoc.configure](#configure)
|
|
101
|
+
- [rundoc.ensure_later](#ensure_later)
|
|
102
|
+
- [rundoc](#configure) an alias for `rundoc.configure`
|
|
101
103
|
- Import and compose documents
|
|
102
104
|
- [rundoc.require](#compose-multiple-rundoc-documents)
|
|
103
105
|
|
|
@@ -257,6 +259,8 @@ However this command would fall on its face:
|
|
|
257
259
|
|
|
258
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.
|
|
259
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
|
+
|
|
260
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:
|
|
261
265
|
|
|
262
266
|
## Dynamic command templating
|
|
@@ -616,7 +620,7 @@ If you need to specify project specific environment variables create a file call
|
|
|
616
620
|
|
|
617
621
|
## Configure
|
|
618
622
|
|
|
619
|
-
You can configure your docs in your docs use the `RunDOC` command
|
|
623
|
+
You can configure your docs in your docs use the `RunDOC` command via `rundoc.configure` (or alias `rundoc`):
|
|
620
624
|
|
|
621
625
|
```
|
|
622
626
|
:::-- rundoc.configure
|
|
@@ -624,6 +628,30 @@ You can configure your docs in your docs use the `RunDOC` command
|
|
|
624
628
|
|
|
625
629
|
Note: Make sure you run this as a hidden command (with `-`).
|
|
626
630
|
|
|
631
|
+
This will give you a Ruby codeblock that executes and gives you access to `Rundoc.configure do |config|` to configure things about your build (such as modifying your markdown document after successful builds).
|
|
632
|
+
|
|
633
|
+
**Define and Re-use logic**
|
|
634
|
+
|
|
635
|
+
Since it's **just ruby** :tm: you can also use it to define shared logic that can be re-used in ERB templates. For example:
|
|
636
|
+
|
|
637
|
+
```
|
|
638
|
+
:::-- rundoc
|
|
639
|
+
def run!(command, quiet: false, error_on_fail: true)
|
|
640
|
+
puts "Running `#{command}`" unless quiet
|
|
641
|
+
output = `#{command}`
|
|
642
|
+
puts "Command `#{command}` output:\n#{output}" unless quiet
|
|
643
|
+
if error_on_fail && !$?.success?
|
|
644
|
+
raise "Error running #{command}. Output:\n#{output}"
|
|
645
|
+
end
|
|
646
|
+
output
|
|
647
|
+
end
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
```
|
|
651
|
+
:::-> print.erb
|
|
652
|
+
Hello <%= run!("heroku whoami") %>!
|
|
653
|
+
```
|
|
654
|
+
|
|
627
655
|
**After Build**
|
|
628
656
|
|
|
629
657
|
This will eval any code you put under that line (in Ruby) when the build was successful but before the contents are finalized on disk. If you want to run some code after you're done building your docs you could use `Rundoc.configure` block and call the `after_build` method like this:
|
|
@@ -658,6 +686,128 @@ Sometimes sensitive info like usernames, email addresses, or passwords may be in
|
|
|
658
686
|
|
|
659
687
|
This command `filter_sensitive` can be called multiple times with different values. Since the config is in Ruby you could iterate over an array of sensitive data
|
|
660
688
|
|
|
689
|
+
## Ensure Later
|
|
690
|
+
|
|
691
|
+
Run a script on EVERY build (success and failure). Used to guarantee resources are cleaned up.
|
|
692
|
+
|
|
693
|
+
- Arguments
|
|
694
|
+
- `dir:` The directory where the script will be run. Must be one of these values:
|
|
695
|
+
- `:cwd` The directory where the command is first invoked. If this directory does not exist when the `rundoc.ensure_later` is invoked, it will raise an error.
|
|
696
|
+
- `:rundoc_root` The tmp directory where the script is being executed. Useful if the directory where the `rundoc.ensure_later` is defined, is deleted by the rundoc script.
|
|
697
|
+
|
|
698
|
+
For example:
|
|
699
|
+
|
|
700
|
+
```
|
|
701
|
+
:::>> $ heroku create
|
|
702
|
+
:::-- rundoc
|
|
703
|
+
@app_name = run!("heroku info --json | jq -r '.app.name'").strip
|
|
704
|
+
|
|
705
|
+
def app_name
|
|
706
|
+
@app_name
|
|
707
|
+
end
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
```ruby
|
|
711
|
+
:::-- rundoc.ensure_later(dir: :cwd)
|
|
712
|
+
if run!("heroku apps").include?(app_name)
|
|
713
|
+
puts "Cleaning up web app #{app_name}"
|
|
714
|
+
run!("heroku apps:destroy #{app_name} --confirm #{app_name}")
|
|
715
|
+
else
|
|
716
|
+
puts "App `#{app_name}` already cleaned, nothing to do"
|
|
717
|
+
end
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
The output of this will not be included in the document. Multiple ensure blocks can be defined and will execute in the order of their definition. Since they'll be executed EVERY time, logic must handle both success and failure cases. This execution occurs before the temp directory is removed, and before any background task shutdown ensure blocks are triggered.
|
|
721
|
+
|
|
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.
|
|
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
|
+
|
|
661
811
|
## Writing a new command
|
|
662
812
|
|
|
663
813
|
> Note: This is an advanced topic and this interface is unstable.
|
data/lib/rundoc/cli.rb
CHANGED
|
@@ -134,6 +134,9 @@ module Rundoc
|
|
|
134
134
|
end
|
|
135
135
|
|
|
136
136
|
def call
|
|
137
|
+
build_success = false
|
|
138
|
+
ensure_later_errors = []
|
|
139
|
+
|
|
137
140
|
io.puts "## Running your docs"
|
|
138
141
|
load_dotenv
|
|
139
142
|
check_directories_empty!
|
|
@@ -165,7 +168,11 @@ module Rundoc
|
|
|
165
168
|
io: io
|
|
166
169
|
)
|
|
167
170
|
output = begin
|
|
168
|
-
|
|
171
|
+
begin
|
|
172
|
+
parser.to_md
|
|
173
|
+
ensure
|
|
174
|
+
ensure_later_errors = Rundoc.run_ensure_later(io: io)
|
|
175
|
+
end
|
|
169
176
|
rescue StandardError, SignalException => e
|
|
170
177
|
io.puts "Received exception: #{e.inspect}, cleaning up before re-raise"
|
|
171
178
|
on_fail
|
|
@@ -174,6 +181,8 @@ module Rundoc
|
|
|
174
181
|
|
|
175
182
|
on_success(output)
|
|
176
183
|
end
|
|
184
|
+
|
|
185
|
+
build_success = true
|
|
177
186
|
ensure
|
|
178
187
|
# Stop any hanging background tasks
|
|
179
188
|
Rundoc::CodeCommand::Background::ProcessSpawn.tasks.each do |name, task|
|
|
@@ -189,6 +198,10 @@ module Rundoc
|
|
|
189
198
|
description: "tmp working directory"
|
|
190
199
|
)
|
|
191
200
|
end
|
|
201
|
+
|
|
202
|
+
if ensure_later_errors.any? && build_success
|
|
203
|
+
raise ensure_later_errors.first
|
|
204
|
+
end
|
|
192
205
|
end
|
|
193
206
|
|
|
194
207
|
private def clean_dir(dir:, description:)
|
|
@@ -7,25 +7,19 @@ class Rundoc::CodeCommand::BashRunner
|
|
|
7
7
|
@line = line
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
capture_stderr = StringIO.new
|
|
14
|
-
$stderr = capture_stderr
|
|
10
|
+
def suppress_chdir_warning
|
|
11
|
+
old_verbose = $VERBOSE
|
|
12
|
+
$VERBOSE = nil
|
|
15
13
|
yield
|
|
16
14
|
ensure
|
|
17
|
-
|
|
18
|
-
$stderr = old_stderr
|
|
19
|
-
capture_string = capture_stderr.string
|
|
20
|
-
warn capture_string if capture_string.each_line.count > 1 || !capture_string.include?("conflicting chdir")
|
|
21
|
-
end
|
|
15
|
+
$VERBOSE = old_verbose
|
|
22
16
|
end
|
|
23
17
|
|
|
24
18
|
def call(env)
|
|
25
19
|
line = @line.sub("cd", "").strip
|
|
26
20
|
@io.puts "running $ cd #{line}"
|
|
27
21
|
|
|
28
|
-
|
|
22
|
+
suppress_chdir_warning do
|
|
29
23
|
Dir.chdir(line)
|
|
30
24
|
end
|
|
31
25
|
|
|
@@ -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
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "erb"
|
|
4
|
+
|
|
5
|
+
class EmptyBinding
|
|
6
|
+
def self.create
|
|
7
|
+
new.empty_binding
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def empty_binding
|
|
11
|
+
binding
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module Rundoc::CodeCommand
|
|
16
|
+
RUNDOC_ERB_BINDINGS = Hash.new { |h, k| h[k] = EmptyBinding.create }
|
|
17
|
+
RUNDOC_DEFAULT_ERB_BINDING = "default"
|
|
18
|
+
end
|
|
@@ -1,21 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class EmptyBinding
|
|
6
|
-
def self.create
|
|
7
|
-
new.empty_binding
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def empty_binding
|
|
11
|
-
binding
|
|
12
|
-
end
|
|
13
|
-
end
|
|
3
|
+
require_relative "../empty_binding"
|
|
14
4
|
|
|
15
5
|
module Rundoc::CodeCommand
|
|
16
|
-
RUNDOC_ERB_BINDINGS = Hash.new { |h, k| h[k] = EmptyBinding.create }
|
|
17
|
-
RUNDOC_DEFAULT_ERB_BINDING = "default"
|
|
18
|
-
|
|
19
6
|
class PrintERBArgs
|
|
20
7
|
attr_reader :line, :binding_name
|
|
21
8
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ::Rundoc::CodeCommand
|
|
4
|
+
class RundocCommand
|
|
5
|
+
class EnsureLaterArgs
|
|
6
|
+
MAPPING = {
|
|
7
|
+
cwd: ->(context:) {
|
|
8
|
+
Dir.pwd
|
|
9
|
+
},
|
|
10
|
+
rundoc_root: ->(context:) {
|
|
11
|
+
context.output_dir.to_s
|
|
12
|
+
}
|
|
13
|
+
}.freeze
|
|
14
|
+
|
|
15
|
+
def initialize(dir:)
|
|
16
|
+
@dir = dir
|
|
17
|
+
@logic = MAPPING[dir] or raise ArgumentError, "Invalid argument dir: #{dir} must be one of #{MAPPING.keys}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call(context:)
|
|
21
|
+
@logic.call(context: context)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def to_s
|
|
25
|
+
@dir.to_s
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class EnsureLaterRunner
|
|
30
|
+
attr_reader :io, :contents
|
|
31
|
+
|
|
32
|
+
def initialize(user_args:, render_command:, render_result:, io:, contents: nil)
|
|
33
|
+
@io = io
|
|
34
|
+
@contents = contents.dup if contents && !contents.empty?
|
|
35
|
+
@dir = user_args
|
|
36
|
+
@binding = RUNDOC_ERB_BINDINGS[RUNDOC_DEFAULT_ERB_BINDING]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def to_md(env = {})
|
|
40
|
+
""
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def call(env = {})
|
|
44
|
+
resolved_dir = @dir.call(context: env[:context])
|
|
45
|
+
|
|
46
|
+
io.puts "Registering ensure_later block (dir: #{@dir} => #{resolved_dir})"
|
|
47
|
+
Rundoc.add_ensure_later(dir: resolved_dir, code: @contents, binding: @binding)
|
|
48
|
+
""
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
Rundoc.register_code_command(
|
|
55
|
+
keyword: :"rundoc.ensure_later",
|
|
56
|
+
args_klass: Rundoc::CodeCommand::RundocCommand::EnsureLaterArgs,
|
|
57
|
+
runner_klass: Rundoc::CodeCommand::RundocCommand::EnsureLaterRunner,
|
|
58
|
+
always_hidden: true
|
|
59
|
+
)
|
|
@@ -17,6 +17,7 @@ module ::Rundoc
|
|
|
17
17
|
@io = io
|
|
18
18
|
@contents = contents.dup if contents && !contents.empty?
|
|
19
19
|
@contents = user_args.code + (@contents || +"")
|
|
20
|
+
@binding = RUNDOC_ERB_BINDINGS[RUNDOC_DEFAULT_ERB_BINDING]
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
def to_md(env = {})
|
|
@@ -25,7 +26,9 @@ module ::Rundoc
|
|
|
25
26
|
|
|
26
27
|
def call(env = {})
|
|
27
28
|
io.puts "Running: #{contents}"
|
|
28
|
-
|
|
29
|
+
Rundoc.capture_stdout_stderr(io) do
|
|
30
|
+
eval(contents, @binding) # rubocop:disable Security/Eval
|
|
31
|
+
end
|
|
29
32
|
""
|
|
30
33
|
end
|
|
31
34
|
end
|
|
@@ -36,3 +39,4 @@ Rundoc.register_code_command(keyword: :rundoc, args_klass: Rundoc::CodeCommand::
|
|
|
36
39
|
Rundoc.register_code_command(keyword: :"rundoc.configure", args_klass: Rundoc::CodeCommand::RundocCommandArgs, runner_klass: Rundoc::CodeCommand::RundocCommandRunner)
|
|
37
40
|
|
|
38
41
|
require "rundoc/code_command/rundoc/require"
|
|
42
|
+
require "rundoc/code_command/rundoc/ensure_later"
|
data/lib/rundoc/code_command.rb
CHANGED
data/lib/rundoc/peg_parser.rb
CHANGED
|
@@ -39,9 +39,16 @@ module Rundoc
|
|
|
39
39
|
).as(:number)
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
rule(:symbol) {
|
|
43
|
+
str(":") >> (
|
|
44
|
+
match("[a-zA-Z_]") >> match("[a-zA-Z0-9_]").repeat
|
|
45
|
+
).as(:symbol)
|
|
46
|
+
}
|
|
47
|
+
|
|
42
48
|
rule(:value) {
|
|
43
49
|
string |
|
|
44
50
|
number |
|
|
51
|
+
symbol |
|
|
45
52
|
str("true").as(true) |
|
|
46
53
|
str("false").as(false) |
|
|
47
54
|
str("nil").as(:nil)
|
|
@@ -175,6 +182,7 @@ module Rundoc
|
|
|
175
182
|
rule(true => simple(:tr)) { true }
|
|
176
183
|
rule(false => simple(:fa)) { false }
|
|
177
184
|
rule(string: simple(:st)) { st.to_s }
|
|
185
|
+
rule(symbol: simple(:sy)) { sy.to_s.to_sym }
|
|
178
186
|
|
|
179
187
|
rule(number: simple(:nb)) {
|
|
180
188
|
/[eE.]/.match?(nb) ? Float(nb) : Integer(nb)
|
data/lib/rundoc/version.rb
CHANGED
data/lib/rundoc.rb
CHANGED
|
@@ -97,6 +97,43 @@ module Rundoc
|
|
|
97
97
|
yield self
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
+
def ensure_later_blocks
|
|
101
|
+
@ensure_later_blocks ||= []
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def add_ensure_later(dir:, code:, binding:)
|
|
105
|
+
ensure_later_blocks << {dir: dir, code: code, binding: binding}
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def run_ensure_later(io:)
|
|
109
|
+
errors = []
|
|
110
|
+
ensure_later_blocks.each do |block|
|
|
111
|
+
io.puts "Running ensure_later block in #{block[:dir]}:\n#{block[:code]}"
|
|
112
|
+
Dir.chdir(block[:dir]) do
|
|
113
|
+
capture_stdout_stderr(io) do
|
|
114
|
+
eval(block[:code], block[:binding]) # rubocop:disable Security/Eval
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
rescue => e
|
|
118
|
+
io.puts "ensure_later block failed in #{block[:dir]}: #{e.message}"
|
|
119
|
+
io.puts e.backtrace.join("\n")
|
|
120
|
+
errors << e
|
|
121
|
+
end
|
|
122
|
+
ensure_later_blocks.clear
|
|
123
|
+
errors
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def capture_stdout_stderr(io)
|
|
127
|
+
old_stdout = $stdout
|
|
128
|
+
old_stderr = $stderr
|
|
129
|
+
$stdout = io
|
|
130
|
+
$stderr = io
|
|
131
|
+
yield
|
|
132
|
+
ensure
|
|
133
|
+
$stdout = old_stdout
|
|
134
|
+
$stderr = old_stderr
|
|
135
|
+
end
|
|
136
|
+
|
|
100
137
|
def filter_sensitive(sensitive)
|
|
101
138
|
raise "Expecting #{sensitive} to be a hash" unless sensitive.is_a?(Hash)
|
|
102
139
|
@sensitive ||= {}
|
data/rundoc.gemspec
CHANGED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class IntegrationEnsureLaterTest < Minitest::Test
|
|
6
|
+
def test_runs_on_success
|
|
7
|
+
Dir.mktmpdir do |dir|
|
|
8
|
+
Dir.chdir(dir) do
|
|
9
|
+
dir = Pathname(dir)
|
|
10
|
+
marker = dir.join("ensure_ran.txt")
|
|
11
|
+
|
|
12
|
+
source_path = dir.join("RUNDOC.md")
|
|
13
|
+
source_path.write <<~EOF
|
|
14
|
+
```
|
|
15
|
+
:::-- rundoc.ensure_later(dir: :cwd)
|
|
16
|
+
File.write("#{marker}", "yes")
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
:::>> $ echo "hello"
|
|
21
|
+
```
|
|
22
|
+
EOF
|
|
23
|
+
|
|
24
|
+
Rundoc::CLI.new(
|
|
25
|
+
io: StringIO.new,
|
|
26
|
+
source_path: source_path,
|
|
27
|
+
on_success_dir: dir.join(SUCCESS_DIRNAME)
|
|
28
|
+
).call
|
|
29
|
+
|
|
30
|
+
assert marker.exist?, "ensure_later block should have run on success"
|
|
31
|
+
assert_equal "yes", marker.read
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def test_runs_on_failure
|
|
37
|
+
Dir.mktmpdir do |dir|
|
|
38
|
+
Dir.chdir(dir) do
|
|
39
|
+
dir = Pathname(dir)
|
|
40
|
+
marker = dir.join("ensure_ran.txt")
|
|
41
|
+
|
|
42
|
+
source_path = dir.join("RUNDOC.md")
|
|
43
|
+
source_path.write <<~EOF
|
|
44
|
+
```
|
|
45
|
+
:::-- rundoc.ensure_later(dir: :cwd)
|
|
46
|
+
File.write("#{marker}", "cleaned")
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
:::>> $ exit 1
|
|
51
|
+
```
|
|
52
|
+
EOF
|
|
53
|
+
|
|
54
|
+
assert_raises do
|
|
55
|
+
Rundoc::CLI.new(
|
|
56
|
+
io: StringIO.new,
|
|
57
|
+
source_path: source_path,
|
|
58
|
+
on_success_dir: dir.join(SUCCESS_DIRNAME),
|
|
59
|
+
on_failure_dir: dir.join(FAILURE_DIRNAME)
|
|
60
|
+
).call
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
assert marker.exist?, "ensure_later block should have run on failure"
|
|
64
|
+
assert_equal "cleaned", marker.read
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def test_multiple_blocks_run_in_order
|
|
70
|
+
Dir.mktmpdir do |dir|
|
|
71
|
+
Dir.chdir(dir) do
|
|
72
|
+
dir = Pathname(dir)
|
|
73
|
+
marker = dir.join("order.txt")
|
|
74
|
+
|
|
75
|
+
source_path = dir.join("RUNDOC.md")
|
|
76
|
+
source_path.write <<~EOF
|
|
77
|
+
```
|
|
78
|
+
:::-- rundoc.ensure_later(dir: :cwd)
|
|
79
|
+
File.write("#{marker}", "first")
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
:::-- rundoc.ensure_later(dir: :cwd)
|
|
84
|
+
File.write("#{marker}", File.read("#{marker}") + ",second")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
:::>> $ echo "hello"
|
|
89
|
+
```
|
|
90
|
+
EOF
|
|
91
|
+
|
|
92
|
+
Rundoc::CLI.new(
|
|
93
|
+
io: StringIO.new,
|
|
94
|
+
source_path: source_path,
|
|
95
|
+
on_success_dir: dir.join(SUCCESS_DIRNAME)
|
|
96
|
+
).call
|
|
97
|
+
|
|
98
|
+
assert_equal "first,second", marker.read
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def test_one_failure_does_not_stop_others
|
|
104
|
+
Dir.mktmpdir do |dir|
|
|
105
|
+
Dir.chdir(dir) do
|
|
106
|
+
dir = Pathname(dir)
|
|
107
|
+
marker = dir.join("second_ran.txt")
|
|
108
|
+
|
|
109
|
+
source_path = dir.join("RUNDOC.md")
|
|
110
|
+
source_path.write <<~EOF
|
|
111
|
+
```
|
|
112
|
+
:::-- rundoc.ensure_later(dir: :cwd)
|
|
113
|
+
raise "intentional failure"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
:::-- rundoc.ensure_later(dir: :cwd)
|
|
118
|
+
File.write("#{marker}", "yes")
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
:::>> $ echo "hello"
|
|
123
|
+
```
|
|
124
|
+
EOF
|
|
125
|
+
|
|
126
|
+
error = assert_raises(RuntimeError) do
|
|
127
|
+
Rundoc::CLI.new(
|
|
128
|
+
io: StringIO.new,
|
|
129
|
+
source_path: source_path,
|
|
130
|
+
on_success_dir: dir.join(SUCCESS_DIRNAME)
|
|
131
|
+
).call
|
|
132
|
+
end
|
|
133
|
+
assert_match(/intentional failure/, error.message)
|
|
134
|
+
|
|
135
|
+
assert marker.exist?, "second ensure_later should still run after first fails"
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def test_success_plus_ensure_failure_is_overall_failure
|
|
141
|
+
Dir.mktmpdir do |dir|
|
|
142
|
+
Dir.chdir(dir) do
|
|
143
|
+
dir = Pathname(dir)
|
|
144
|
+
|
|
145
|
+
source_path = dir.join("RUNDOC.md")
|
|
146
|
+
source_path.write <<~EOF
|
|
147
|
+
```
|
|
148
|
+
:::-- rundoc.ensure_later(dir: :cwd)
|
|
149
|
+
raise "cleanup failed"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
:::>> $ echo "hello"
|
|
154
|
+
```
|
|
155
|
+
EOF
|
|
156
|
+
|
|
157
|
+
error = assert_raises(RuntimeError) do
|
|
158
|
+
Rundoc::CLI.new(
|
|
159
|
+
io: StringIO.new,
|
|
160
|
+
source_path: source_path,
|
|
161
|
+
on_success_dir: dir.join(SUCCESS_DIRNAME)
|
|
162
|
+
).call
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
assert_match(/cleanup failed/, error.message)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def test_dir_rundoc_root
|
|
171
|
+
Dir.mktmpdir do |dir|
|
|
172
|
+
Dir.chdir(dir) do
|
|
173
|
+
dir = Pathname(dir)
|
|
174
|
+
marker = dir.join("root_marker.txt")
|
|
175
|
+
|
|
176
|
+
source_path = dir.join("RUNDOC.md")
|
|
177
|
+
source_path.write <<~EOF
|
|
178
|
+
```
|
|
179
|
+
:::>> $ mkdir subdir
|
|
180
|
+
:::>> $ cd subdir
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
:::-- rundoc.ensure_later(dir: :rundoc_root)
|
|
185
|
+
File.write("#{marker}", Dir.pwd)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
:::>> $ echo "hello"
|
|
190
|
+
```
|
|
191
|
+
EOF
|
|
192
|
+
|
|
193
|
+
cli = Rundoc::CLI.new(
|
|
194
|
+
io: StringIO.new,
|
|
195
|
+
source_path: source_path,
|
|
196
|
+
on_success_dir: dir.join(SUCCESS_DIRNAME)
|
|
197
|
+
)
|
|
198
|
+
output_dir = cli.execution_context.output_dir.realpath.to_s
|
|
199
|
+
|
|
200
|
+
cli.call
|
|
201
|
+
|
|
202
|
+
assert marker.exist?, "ensure_later with dir: :rundoc_root should run"
|
|
203
|
+
assert_equal output_dir, marker.read
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def test_output_not_in_document
|
|
209
|
+
Dir.mktmpdir do |dir|
|
|
210
|
+
Dir.chdir(dir) do
|
|
211
|
+
dir = Pathname(dir)
|
|
212
|
+
|
|
213
|
+
source_path = dir.join("RUNDOC.md")
|
|
214
|
+
source_path.write <<~EOF
|
|
215
|
+
```
|
|
216
|
+
:::-- rundoc.ensure_later(dir: :cwd)
|
|
217
|
+
puts "THIS SHOULD NOT APPEAR"
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
:::>> $ echo "visible"
|
|
222
|
+
```
|
|
223
|
+
EOF
|
|
224
|
+
|
|
225
|
+
Rundoc::CLI.new(
|
|
226
|
+
io: StringIO.new,
|
|
227
|
+
source_path: source_path,
|
|
228
|
+
on_success_dir: dir.join(SUCCESS_DIRNAME)
|
|
229
|
+
).call
|
|
230
|
+
|
|
231
|
+
readme = dir.join(SUCCESS_DIRNAME).join("README.md").read
|
|
232
|
+
refute_match(/THIS SHOULD NOT APPEAR/, readme)
|
|
233
|
+
assert_match(/visible/, readme)
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def test_invalid_dir_raises
|
|
239
|
+
Dir.mktmpdir do |dir|
|
|
240
|
+
Dir.chdir(dir) do
|
|
241
|
+
dir = Pathname(dir)
|
|
242
|
+
|
|
243
|
+
source_path = dir.join("RUNDOC.md")
|
|
244
|
+
source_path.write <<~EOF
|
|
245
|
+
```
|
|
246
|
+
:::-- rundoc.ensure_later(dir: :invalid)
|
|
247
|
+
puts "should not run"
|
|
248
|
+
```
|
|
249
|
+
EOF
|
|
250
|
+
|
|
251
|
+
assert_raises(ArgumentError) do
|
|
252
|
+
Rundoc::CLI.new(
|
|
253
|
+
io: StringIO.new,
|
|
254
|
+
source_path: source_path,
|
|
255
|
+
on_success_dir: dir.join(SUCCESS_DIRNAME)
|
|
256
|
+
).call
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def test_shared_binding_with_rundoc
|
|
263
|
+
Dir.mktmpdir do |dir|
|
|
264
|
+
Dir.chdir(dir) do
|
|
265
|
+
dir = Pathname(dir)
|
|
266
|
+
marker = dir.join("binding_test.txt")
|
|
267
|
+
|
|
268
|
+
source_path = dir.join("RUNDOC.md")
|
|
269
|
+
source_path.write <<~EOF
|
|
270
|
+
```
|
|
271
|
+
:::-- rundoc
|
|
272
|
+
def my_helper
|
|
273
|
+
"from_rundoc"
|
|
274
|
+
end
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
```
|
|
278
|
+
:::-- rundoc.ensure_later(dir: :cwd)
|
|
279
|
+
File.write("#{marker}", my_helper)
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
```
|
|
283
|
+
:::>> $ echo "hello"
|
|
284
|
+
```
|
|
285
|
+
EOF
|
|
286
|
+
|
|
287
|
+
Rundoc::CLI.new(
|
|
288
|
+
io: StringIO.new,
|
|
289
|
+
source_path: source_path,
|
|
290
|
+
on_success_dir: dir.join(SUCCESS_DIRNAME)
|
|
291
|
+
).call
|
|
292
|
+
|
|
293
|
+
assert_equal "from_rundoc", marker.read
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def test_runs_on_failure_from_subdirectory
|
|
299
|
+
Dir.mktmpdir do |dir|
|
|
300
|
+
Dir.chdir(dir) do
|
|
301
|
+
dir = Pathname(dir)
|
|
302
|
+
marker = dir.join("ensure_ran.txt")
|
|
303
|
+
|
|
304
|
+
source_path = dir.join("RUNDOC.md")
|
|
305
|
+
source_path.write <<~EOF
|
|
306
|
+
```
|
|
307
|
+
:::>> $ mkdir myapp
|
|
308
|
+
:::>> $ cd myapp
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
:::-- rundoc.ensure_later(dir: :cwd)
|
|
313
|
+
File.write("#{marker}", "cleaned")
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
```
|
|
317
|
+
:::>> $ exit 1
|
|
318
|
+
```
|
|
319
|
+
EOF
|
|
320
|
+
|
|
321
|
+
assert_raises do
|
|
322
|
+
Rundoc::CLI.new(
|
|
323
|
+
io: StringIO.new,
|
|
324
|
+
source_path: source_path,
|
|
325
|
+
on_success_dir: dir.join(SUCCESS_DIRNAME),
|
|
326
|
+
on_failure_dir: dir.join(FAILURE_DIRNAME)
|
|
327
|
+
).call
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
assert marker.exist?, "ensure_later from subdirectory should run even on failure"
|
|
331
|
+
assert_equal "cleaned", marker.read
|
|
332
|
+
end
|
|
333
|
+
end
|
|
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
|
|
365
|
+
end
|
|
@@ -50,6 +50,57 @@ class IntegrationPrintTest < Minitest::Test
|
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
def test_rundoc_configure_defines_variable_accessible_from_erb
|
|
54
|
+
key = SecureRandom.hex
|
|
55
|
+
contents = <<~RUBY
|
|
56
|
+
```
|
|
57
|
+
:::-- rundoc.configure
|
|
58
|
+
@shared_value = "#{key}"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
:::-> print.erb
|
|
63
|
+
<%= @shared_value %>
|
|
64
|
+
```
|
|
65
|
+
RUBY
|
|
66
|
+
|
|
67
|
+
Dir.mktmpdir do |dir|
|
|
68
|
+
Dir.chdir(dir) do
|
|
69
|
+
parsed = parse_contents(contents)
|
|
70
|
+
actual = parsed.to_md.gsub(Rundoc::FencedCodeBlock::AUTOGEN_WARNING, "")
|
|
71
|
+
assert_includes actual, key
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def test_erb_defines_variable_accessible_from_rundoc_configure
|
|
77
|
+
key = SecureRandom.hex
|
|
78
|
+
contents = <<~RUBY
|
|
79
|
+
```
|
|
80
|
+
:::-> print.erb
|
|
81
|
+
<% @from_erb = "#{key}" %>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
:::-- rundoc.configure
|
|
86
|
+
@roundtripped = @from_erb + "_via_configure"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
:::-> print.erb
|
|
91
|
+
<%= @roundtripped %>
|
|
92
|
+
```
|
|
93
|
+
RUBY
|
|
94
|
+
|
|
95
|
+
Dir.mktmpdir do |dir|
|
|
96
|
+
Dir.chdir(dir) do
|
|
97
|
+
parsed = parse_contents(contents)
|
|
98
|
+
actual = parsed.to_md.gsub(Rundoc::FencedCodeBlock::AUTOGEN_WARNING, "")
|
|
99
|
+
assert_includes actual, "#{key}_via_configure"
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
53
104
|
def test_erb_in_block
|
|
54
105
|
contents = <<~RUBY
|
|
55
106
|
```
|
|
@@ -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(
|
|
@@ -367,4 +367,29 @@ class PegParserTest < Minitest::Test
|
|
|
367
367
|
assert_equal :rundoc, actual.keyword
|
|
368
368
|
assert_equal "first = 1 # comment\nsecond = 2".strip, actual.contents.strip
|
|
369
369
|
end
|
|
370
|
+
|
|
371
|
+
def test_symbol_value
|
|
372
|
+
input = %(:cwd)
|
|
373
|
+
parser = Rundoc::PegParser.new.symbol
|
|
374
|
+
tree = parser.parse_with_debug(input)
|
|
375
|
+
actual = @transformer.apply(tree)
|
|
376
|
+
assert_equal :cwd, actual
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def test_symbol_in_named_args
|
|
380
|
+
input = %(dir: :cwd)
|
|
381
|
+
parser = Rundoc::PegParser.new.named_args
|
|
382
|
+
tree = parser.parse_with_debug(input)
|
|
383
|
+
actual = @transformer.apply(tree)
|
|
384
|
+
assert_equal({dir: :cwd}, actual)
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def test_symbol_in_method_call
|
|
388
|
+
input = %(rundoc.ensure_later(dir: :cwd))
|
|
389
|
+
parser = Rundoc::PegParser.new.method_call
|
|
390
|
+
tree = parser.parse_with_debug(input)
|
|
391
|
+
actual = @transformer.apply(tree)
|
|
392
|
+
assert_equal :"rundoc.ensure_later", actual.keyword
|
|
393
|
+
assert_equal({dir: :cwd}, actual.original_args)
|
|
394
|
+
end
|
|
370
395
|
end
|
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
|
|
@@ -107,6 +108,20 @@ dependencies:
|
|
|
107
108
|
- - ">="
|
|
108
109
|
- !ruby/object:Gem::Version
|
|
109
110
|
version: '0'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: cgi
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - ">="
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: 0.3.6
|
|
118
|
+
type: :runtime
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - ">="
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: 0.3.6
|
|
110
125
|
- !ruby/object:Gem::Dependency
|
|
111
126
|
name: rake
|
|
112
127
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -212,6 +227,7 @@ files:
|
|
|
212
227
|
- lib/rundoc/code_command/bash/cd.rb
|
|
213
228
|
- lib/rundoc/code_command/comment.rb
|
|
214
229
|
- lib/rundoc/code_command/deferred.rb
|
|
230
|
+
- lib/rundoc/code_command/empty_binding.rb
|
|
215
231
|
- lib/rundoc/code_command/file_command/append.rb
|
|
216
232
|
- lib/rundoc/code_command/file_command/remove.rb
|
|
217
233
|
- lib/rundoc/code_command/no_such_command.rb
|
|
@@ -220,6 +236,7 @@ files:
|
|
|
220
236
|
- lib/rundoc/code_command/print/erb.rb
|
|
221
237
|
- lib/rundoc/code_command/print/text.rb
|
|
222
238
|
- lib/rundoc/code_command/raw.rb
|
|
239
|
+
- lib/rundoc/code_command/rundoc/ensure_later.rb
|
|
223
240
|
- lib/rundoc/code_command/rundoc/require.rb
|
|
224
241
|
- lib/rundoc/code_command/rundoc_command.rb
|
|
225
242
|
- lib/rundoc/code_command/website.rb
|
|
@@ -262,6 +279,7 @@ files:
|
|
|
262
279
|
- test/fixtures/simple_git/rundoc.md
|
|
263
280
|
- test/integration/after_build_test.rb
|
|
264
281
|
- test/integration/background_stdin_test.rb
|
|
282
|
+
- test/integration/ensure_later_test.rb
|
|
265
283
|
- test/integration/failure_test.rb
|
|
266
284
|
- test/integration/pre_erb_test.rb
|
|
267
285
|
- test/integration/print_test.rb
|
|
@@ -287,6 +305,7 @@ homepage: https://github.com/schneems/rundoc
|
|
|
287
305
|
licenses:
|
|
288
306
|
- MIT
|
|
289
307
|
metadata: {}
|
|
308
|
+
post_install_message:
|
|
290
309
|
rdoc_options: []
|
|
291
310
|
require_paths:
|
|
292
311
|
- lib
|
|
@@ -301,7 +320,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
301
320
|
- !ruby/object:Gem::Version
|
|
302
321
|
version: '0'
|
|
303
322
|
requirements: []
|
|
304
|
-
rubygems_version: 3.
|
|
323
|
+
rubygems_version: 3.4.19
|
|
324
|
+
signing_key:
|
|
305
325
|
specification_version: 4
|
|
306
326
|
summary: RunDOC generates runable code from docs
|
|
307
327
|
test_files: []
|