rundoc 4.1.4 → 6.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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +3 -1
  3. data/.standard.yml +1 -1
  4. data/CHANGELOG.md +12 -0
  5. data/README.md +98 -10
  6. data/lib/rundoc/cli.rb +18 -2
  7. data/lib/rundoc/code_command/background/log/clear.rb +12 -2
  8. data/lib/rundoc/code_command/background/log/read.rb +12 -2
  9. data/lib/rundoc/code_command/background/process_spawn.rb +3 -1
  10. data/lib/rundoc/code_command/background/start.rb +25 -6
  11. data/lib/rundoc/code_command/background/stdin_write.rb +21 -8
  12. data/lib/rundoc/code_command/background/stop.rb +12 -2
  13. data/lib/rundoc/code_command/background/wait.rb +15 -3
  14. data/lib/rundoc/code_command/background.rb +2 -0
  15. data/lib/rundoc/code_command/bash/cd.rb +12 -18
  16. data/lib/rundoc/code_command/bash.rb +43 -19
  17. data/lib/rundoc/code_command/comment.rb +33 -0
  18. data/lib/rundoc/code_command/deferred.rb +66 -0
  19. data/lib/rundoc/code_command/empty_binding.rb +18 -0
  20. data/lib/rundoc/code_command/file_command/append.rb +29 -8
  21. data/lib/rundoc/code_command/file_command/remove.rb +27 -5
  22. data/lib/rundoc/code_command/no_such_command.rb +8 -3
  23. data/lib/rundoc/code_command/pipe.rb +36 -16
  24. data/lib/rundoc/code_command/pre/erb.rb +28 -18
  25. data/lib/rundoc/code_command/print/erb.rb +27 -16
  26. data/lib/rundoc/code_command/print/text.rb +27 -8
  27. data/lib/rundoc/code_command/raw.rb +17 -5
  28. data/lib/rundoc/code_command/rundoc/ensure_later.rb +59 -0
  29. data/lib/rundoc/code_command/rundoc/require.rb +25 -17
  30. data/lib/rundoc/code_command/rundoc_command.rb +26 -9
  31. data/lib/rundoc/code_command/website/driver.rb +2 -0
  32. data/lib/rundoc/code_command/website/navigate.rb +18 -12
  33. data/lib/rundoc/code_command/website/screenshot.rb +17 -11
  34. data/lib/rundoc/code_command/website/visit.rb +23 -12
  35. data/lib/rundoc/code_command/website.rb +2 -0
  36. data/lib/rundoc/code_command/write.rb +37 -9
  37. data/lib/rundoc/code_command.rb +6 -48
  38. data/lib/rundoc/context/after_build.rb +2 -0
  39. data/lib/rundoc/context/execution.rb +2 -0
  40. data/lib/rundoc/document.rb +6 -2
  41. data/lib/rundoc/fenced_code_block.rb +10 -7
  42. data/lib/rundoc/peg_parser.rb +25 -9
  43. data/lib/rundoc/version.rb +3 -1
  44. data/lib/rundoc.rb +89 -17
  45. data/rundoc.gemspec +3 -0
  46. data/test/integration/ensure_later_test.rb +335 -0
  47. data/test/integration/print_test.rb +51 -0
  48. data/test/rundoc/code_commands/append_file_test.rb +35 -10
  49. data/test/rundoc/code_commands/background_test.rb +24 -22
  50. data/test/rundoc/code_commands/bash_test.rb +10 -5
  51. data/test/rundoc/code_commands/comment_test.rb +116 -0
  52. data/test/rundoc/code_commands/pipe_test.rb +2 -2
  53. data/test/rundoc/code_commands/print_test.rb +13 -25
  54. data/test/rundoc/code_commands/remove_contents_test.rb +8 -3
  55. data/test/rundoc/code_section_test.rb +28 -21
  56. data/test/rundoc/peg_parser_test.rb +42 -1
  57. data/test/test_helper.rb +4 -2
  58. metadata +23 -6
  59. data/lib/rundoc/code_command/rundoc/depend_on.rb +0 -13
  60. data/test/fixtures/depend_on/dependency/rundoc.md +0 -5
  61. data/test/fixtures/depend_on/main/rundoc.md +0 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 83fd435266e63543e96c3e24fc4d116cd043f2c89e396b8839d283eace07248a
4
- data.tar.gz: 9818843a327f3ed3c4a46548db1dc8e9b2c706824716749d7a173563a752d6ae
3
+ metadata.gz: 498d5109daa7abb733fc5c27cf583bb1c84cc7b92bbccaaf9e005a51a6ec0555
4
+ data.tar.gz: fc72016241033a945486b5fa893b3f2cad0c8b3c7a2c44eab3f964adb7308f9e
5
5
  SHA512:
6
- metadata.gz: f5972a3213f3700659aa2d9ca602821f0cb443e9e56120b5e43485da6b0240a4b7b998dea1b0637b470ac098d0fab5c7ee7922b056eebfd08575c0177bc312d9
7
- data.tar.gz: c58f0fdf37e46817eb28871e1136811b0dd2c2a12925948e461f286b47de8c2b3bae1ba06345956908ac09c859ad0d1fe284d10a2c2a019516ba07a134103d07
6
+ metadata.gz: 5af654672de3e6051ec46377052c55b55207ce13eb29cf977d1cf793291e953d28e842fd79b0f0972f8eb83a98c6671c1834a5ac75f20da795eb686a99a02c52
7
+ data.tar.gz: b2acdf08759aa0c6f73410a00a4ad387da198f1f4497228c69d27dd799799e12107cc421c8f3fd5242f9517dbfd2565e71b56e2b3a6ffd77ffd879906c902d65
@@ -11,9 +11,10 @@ jobs:
11
11
  fail-fast: false
12
12
  matrix:
13
13
  ruby:
14
- - 3.1
15
14
  - 3.2
16
15
  - 3.3
16
+ - 3.4
17
+ - "4.0"
17
18
  - head
18
19
  steps:
19
20
  - name: Checkout code
@@ -34,6 +35,7 @@ jobs:
34
35
  matrix:
35
36
  ruby:
36
37
  - 3.3
38
+ - "4.0"
37
39
  - head
38
40
  steps:
39
41
  - name: Checkout code
data/.standard.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  fix: true # default: false
2
2
  parallel: true # default: false
3
3
  format: progress # default: Standard::Formatter
4
- ruby_version: 3.1 # default: RUBY_VERSION
4
+ ruby_version: 3.2 # default: RUBY_VERSION
5
5
  ignore: # default: []
6
6
  - 'test/fixtures/**/*'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  ## HEAD
2
2
 
3
+ ## 6.0.0
4
+
5
+ - 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.
6
+ - 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.
7
+
8
+ ## 5.0.0
9
+
10
+ - Added comment syntax. Use an octothorpe (`#`) after the visibility markers to comment out any commands and make them a no-op.
11
+ - Changed: Minimum Ruby version is now 3.2 (Ruby 3.1 reached EOL in March 2025).
12
+ - Changed: All code commands now use an Args + Runner class pattern. `register_code_command` now requires keyword arguments: `keyword:`, `args_klass:`, and `runner_klass:`.
13
+ - Fix: `seattle_method` parser rule no longer matches across newlines. Previously, a command with no same-line arguments (e.g. `:::>> rundoc`) would consume the next line as its argument, losing the newline between content lines. (https://github.com/zombocom/rundoc/pull/118)
14
+
3
15
  ## 4.1.4
4
16
 
5
17
  - Fix: Net::ReadTimeout errors on `website.visit` are now retried by default (https://github.com/zombocom/rundoc/pull/103)
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
 
@@ -616,7 +618,7 @@ If you need to specify project specific environment variables create a file call
616
618
 
617
619
  ## Configure
618
620
 
619
- You can configure your docs in your docs use the `RunDOC` command
621
+ You can configure your docs in your docs use the `RunDOC` command via `rundoc.configure` (or alias `rundoc`):
620
622
 
621
623
  ```
622
624
  :::-- rundoc.configure
@@ -624,6 +626,30 @@ You can configure your docs in your docs use the `RunDOC` command
624
626
 
625
627
  Note: Make sure you run this as a hidden command (with `-`).
626
628
 
629
+ 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).
630
+
631
+ **Define and Re-use logic**
632
+
633
+ 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:
634
+
635
+ ```
636
+ :::-- rundoc
637
+ def run!(command, quiet: false, error_on_fail: true)
638
+ puts "Running `#{command}`" unless quiet
639
+ output = `#{command}`
640
+ puts "Command `#{command}` output:\n#{output}" unless quiet
641
+ if error_on_fail && !$?.success?
642
+ raise "Error running #{command}. Output:\n#{output}"
643
+ end
644
+ output
645
+ end
646
+ ```
647
+
648
+ ```
649
+ :::-> print.erb
650
+ Hello <%= run!("heroku whoami") %>!
651
+ ```
652
+
627
653
  **After Build**
628
654
 
629
655
  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,38 +684,100 @@ Sometimes sensitive info like usernames, email addresses, or passwords may be in
658
684
 
659
685
  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
686
 
687
+ ## Ensure Later
688
+
689
+ Run a script on EVERY build (success and failure). Used to guarantee resources are cleaned up.
690
+
691
+ - Arguments
692
+ - `dir:` The directory where the script will be run. Must be one of these values:
693
+ - `: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.
694
+ - `: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.
695
+
696
+ For example:
697
+
698
+ ```
699
+ :::>> $ heroku create
700
+ :::-- rundoc
701
+ @app_name = run!("heroku info --json | jq -r '.app.name'").strip
702
+
703
+ def app_name
704
+ @app_name
705
+ end
706
+ ```
707
+
708
+ ```ruby
709
+ :::-- rundoc.ensure_later(dir: :cwd)
710
+ if run!("heroku apps").include?(app_name)
711
+ puts "Cleaning up web app #{app_name}"
712
+ run!("heroku apps:destroy #{app_name} --confirm #{app_name}")
713
+ else
714
+ puts "App `#{app_name}` already cleaned, nothing to do"
715
+ end
716
+ ```
717
+
718
+ 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.
719
+
720
+ 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
+
661
722
  ## Writing a new command
662
723
 
724
+ > Note: This is an advanced topic and this interface is unstable.
725
+
663
726
  Rundoc does not have a stable internal command interface. You can define your own commands, but unless it is committed in this repo, it may break on a minor version change.
664
727
 
665
728
  To add a new command it needs to be parsed and called. Examples of commands being implemented are seen in `lib/rundoc/code_command`.
666
729
 
667
- A new command needs to be registered:
730
+ Each command is split into two classes: an **Args** class that handles argument parsing/validation and a **Runner** class that handles execution. Both must be registered:
668
731
 
732
+ ```ruby
733
+ Rundoc.register_code_command(
734
+ keyword: :lol,
735
+ args_klass: Rundoc::CodeCommand::LolArgs,
736
+ runner_klass: Rundoc::CodeCommand::LolRunner
737
+ )
669
738
  ```
670
- Rundoc.register_code_command(:lol, Rundoc::CodeCommand::Lol)
671
- ```
672
739
 
673
- They should inherit from Rundoc::CodeCommand:
740
+ The Args class is a plain Ruby class. Its initialize method receives input from the document and exposes parsed values via `attr_reader`:
741
+
742
+ ```ruby
743
+ class Rundoc::CodeCommand::LolArgs
744
+ attr_reader :message
674
745
 
746
+ def initialize(message)
747
+ @message = message
748
+ end
749
+ end
675
750
  ```
676
- class Rundoc::CodeCommand::Lol < Rundoc::CodeCommand
677
- def initialize(line)
751
+
752
+ The Runner class is namespaced under `Rundoc::CodeCommand` and receives the args instance via `user_args:`:
753
+
754
+ ```ruby
755
+ class Rundoc::CodeCommand::LolRunner
756
+ def initialize(user_args:, **)
757
+ @message = user_args.message
758
+ end
759
+
760
+ def to_md(env = {})
761
+ ""
762
+ end
763
+
764
+ def call(env = {})
765
+ @message
678
766
  end
679
767
  end
680
768
  ```
681
769
 
682
- The initialize method is called with input from the document. The command is rendered (`:::>-`) by the output of the `def call` method. The contents produced by the command (`:::->`) are rendered by the `def to_md` method.
770
+ The command is rendered (`:::>-`) by the output of the `def call` method. The contents produced by the command (`:::->`) are rendered by the `def to_md` method.
683
771
 
684
772
  The syntax for commands is ruby-ish but it is a custom grammar implemented in `lib/peg_parser.rb` for more info on manipulating the grammar see this tutorial on how I added keword-like/hash-like syntax https://github.com/schneems/implement_ruby_hash_syntax_with_parslet_example.
685
773
 
686
- Command initialize methods natively support:
774
+ Args class initialize methods natively support:
687
775
 
688
776
  - Barewords as a single string input
689
777
  - Keyword arguments
690
778
  - A combination of the two
691
779
 
692
- Anything that is passed to the command via "stdin" is available via a method `self.contents`. The interplay between the input and `self.contents` is not strongly defined.
780
+ Anything that is passed to the command via "stdin" is available on the Runner via a method `self.contents`. The interplay between the input and `self.contents` is not strongly defined.
693
781
 
694
782
  ## Copyright
695
783
 
data/lib/rundoc/cli.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rundoc
2
4
  class CLI
3
5
  module DEFAULTS
@@ -132,6 +134,9 @@ module Rundoc
132
134
  end
133
135
 
134
136
  def call
137
+ build_success = false
138
+ ensure_later_errors = []
139
+
135
140
  io.puts "## Running your docs"
136
141
  load_dotenv
137
142
  check_directories_empty!
@@ -159,10 +164,15 @@ module Rundoc
159
164
  Dir.chdir(execution_context.output_dir) do
160
165
  parser = Rundoc::Document.new(
161
166
  source_contents,
162
- context: execution_context
167
+ context: execution_context,
168
+ io: io
163
169
  )
164
170
  output = begin
165
- parser.to_md
171
+ begin
172
+ parser.to_md
173
+ ensure
174
+ ensure_later_errors = Rundoc.run_ensure_later(io: io)
175
+ end
166
176
  rescue StandardError, SignalException => e
167
177
  io.puts "Received exception: #{e.inspect}, cleaning up before re-raise"
168
178
  on_fail
@@ -171,6 +181,8 @@ module Rundoc
171
181
 
172
182
  on_success(output)
173
183
  end
184
+
185
+ build_success = true
174
186
  ensure
175
187
  # Stop any hanging background tasks
176
188
  Rundoc::CodeCommand::Background::ProcessSpawn.tasks.each do |name, task|
@@ -186,6 +198,10 @@ module Rundoc
186
198
  description: "tmp working directory"
187
199
  )
188
200
  end
201
+
202
+ if ensure_later_errors.any? && build_success
203
+ raise ensure_later_errors.first
204
+ end
189
205
  end
190
206
 
191
207
  private def clean_dir(dir:, description:)
@@ -1,7 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Rundoc::CodeCommand::Background::Log
2
- class Clear < Rundoc::CodeCommand
4
+ class ClearArgs
5
+ attr_reader :name
6
+
3
7
  def initialize(name:)
4
8
  @name = name
9
+ end
10
+ end
11
+
12
+ class ClearRunner
13
+ def initialize(user_args:, render_command:, render_result:, io: nil, contents: nil)
14
+ @name = user_args.name
5
15
  @background = nil
6
16
  end
7
17
 
@@ -19,4 +29,4 @@ class Rundoc::CodeCommand::Background::Log
19
29
  end
20
30
  end
21
31
  end
22
- Rundoc.register_code_command(:"background.log.clear", Rundoc::CodeCommand::Background::Log::Clear)
32
+ Rundoc.register_code_command(keyword: :"background.log.clear", args_klass: Rundoc::CodeCommand::Background::Log::ClearArgs, runner_klass: Rundoc::CodeCommand::Background::Log::ClearRunner)
@@ -1,7 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Rundoc::CodeCommand::Background::Log
2
- class Read < Rundoc::CodeCommand
4
+ class ReadArgs
5
+ attr_reader :name
6
+
3
7
  def initialize(name:)
4
8
  @name = name
9
+ end
10
+ end
11
+
12
+ class ReadRunner
13
+ def initialize(user_args:, render_command:, render_result:, io: nil, contents: nil)
14
+ @name = user_args.name
5
15
  @background = nil
6
16
  end
7
17
 
@@ -18,4 +28,4 @@ class Rundoc::CodeCommand::Background::Log
18
28
  end
19
29
  end
20
30
  end
21
- Rundoc.register_code_command(:"background.log.read", Rundoc::CodeCommand::Background::Log::Read)
31
+ Rundoc.register_code_command(keyword: :"background.log.read", args_klass: Rundoc::CodeCommand::Background::Log::ReadArgs, runner_klass: Rundoc::CodeCommand::Background::Log::ReadRunner)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "shellwords"
2
4
  require "timeout"
3
5
  require "fileutils"
@@ -126,7 +128,7 @@ class Rundoc::CodeCommand::Background
126
128
  Process.kill("TERM", -Process.getpgid(@pid))
127
129
  Process.wait(@pid)
128
130
  rescue Errno::ESRCH => e
129
- puts "Error stopping process (command: #{command}): #{e}"
131
+ print_io&.puts "Error stopping process (command: #{command}): #{e}"
130
132
  ensure
131
133
  print_io&.puts "Log contents for `#{command}`:\n#{@log.read}"
132
134
  end
@@ -1,18 +1,37 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "tempfile"
2
4
 
3
5
  class Rundoc::CodeCommand::Background
4
- class Start < Rundoc::CodeCommand
6
+ class StartArgs
7
+ attr_reader :command, :name, :wait, :timeout, :log, :out, :allow_fail
8
+
5
9
  def initialize(command, name:, wait: nil, timeout: 5, log: Tempfile.new("log"), out: "2>&1", allow_fail: false)
6
- @timeout = timeout
7
10
  @command = command
8
11
  @name = name
9
12
  @wait = wait
10
- @allow_fail = allow_fail
13
+ @timeout = timeout
11
14
  @log = log
12
- @redirect = out
15
+ @out = out
16
+ @allow_fail = allow_fail
17
+ end
18
+ end
19
+
20
+ class StartRunner
21
+ attr_reader :io
22
+
23
+ def initialize(user_args:, render_command:, render_result:, io:, contents: nil)
24
+ @timeout = user_args.timeout
25
+ @command = user_args.command
26
+ @name = user_args.name
27
+ @wait = user_args.wait
28
+ @allow_fail = user_args.allow_fail
29
+ @log = user_args.log
30
+ @redirect = user_args.out
13
31
  FileUtils.touch(@log)
14
32
 
15
33
  @background = nil
34
+ @io = io
16
35
  end
17
36
 
18
37
  def background
@@ -22,7 +41,7 @@ class Rundoc::CodeCommand::Background
22
41
  log: @log,
23
42
  out: @redirect
24
43
  ).tap do |spawn|
25
- puts "Spawning commmand: `#{spawn.command}`"
44
+ io.puts "Spawning commmand: `#{spawn.command}`"
26
45
  ProcessSpawn.add(@name, spawn)
27
46
  end
28
47
  end
@@ -44,4 +63,4 @@ class Rundoc::CodeCommand::Background
44
63
  end
45
64
  end
46
65
 
47
- Rundoc.register_code_command(:"background.start", Rundoc::CodeCommand::Background::Start)
66
+ Rundoc.register_code_command(keyword: :"background.start", args_klass: Rundoc::CodeCommand::Background::StartArgs, runner_klass: Rundoc::CodeCommand::Background::StartRunner)
@@ -1,14 +1,27 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Rundoc::CodeCommand::Background
2
- # Will send contents to the background process via STDIN along with a newline
3
- #
4
- #
5
- class StdinWrite < Rundoc::CodeCommand
4
+ class StdinWriteArgs
5
+ attr_reader :contents, :name, :wait, :timeout, :ending
6
+
6
7
  def initialize(contents, name:, wait:, timeout: 5, ending: $/)
7
8
  @contents = contents
8
- @ending = ending
9
- @wait = wait
10
9
  @name = name
11
- @timeout_value = Integer(timeout)
10
+ @wait = wait
11
+ @timeout = Integer(timeout)
12
+ @ending = ending
13
+ end
14
+ end
15
+
16
+ class StdinWriteRunner
17
+ attr_reader :contents
18
+
19
+ def initialize(user_args:, render_command:, render_result:, io: nil, contents: nil)
20
+ @contents = user_args.contents
21
+ @ending = user_args.ending
22
+ @wait = user_args.wait
23
+ @name = user_args.name
24
+ @timeout_value = user_args.timeout
12
25
  @contents_written = nil
13
26
  @background = nil
14
27
  end
@@ -38,4 +51,4 @@ class Rundoc::CodeCommand::Background
38
51
  end
39
52
  end
40
53
  end
41
- Rundoc.register_code_command(:"background.stdin_write", Rundoc::CodeCommand::Background::StdinWrite)
54
+ Rundoc.register_code_command(keyword: :"background.stdin_write", args_klass: Rundoc::CodeCommand::Background::StdinWriteArgs, runner_klass: Rundoc::CodeCommand::Background::StdinWriteRunner)
@@ -1,7 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Rundoc::CodeCommand::Background
2
- class Stop < Rundoc::CodeCommand
4
+ class StopArgs
5
+ attr_reader :name
6
+
3
7
  def initialize(name:)
4
8
  @name = name
9
+ end
10
+ end
11
+
12
+ class StopRunner
13
+ def initialize(user_args:, render_command:, render_result:, io: nil, contents: nil)
14
+ @name = user_args.name
5
15
  @background = nil
6
16
  end
7
17
 
@@ -19,4 +29,4 @@ class Rundoc::CodeCommand::Background
19
29
  end
20
30
  end
21
31
  end
22
- Rundoc.register_code_command(:"background.stop", Rundoc::CodeCommand::Background::Stop)
32
+ Rundoc.register_code_command(keyword: :"background.stop", args_klass: Rundoc::CodeCommand::Background::StopArgs, runner_klass: Rundoc::CodeCommand::Background::StopRunner)
@@ -1,9 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Rundoc::CodeCommand::Background
2
- class Wait < Rundoc::CodeCommand
4
+ class WaitArgs
5
+ attr_reader :name, :wait, :timeout
6
+
3
7
  def initialize(name:, wait:, timeout: 5)
4
8
  @name = name
5
9
  @wait = wait
6
- @timeout_value = Integer(timeout)
10
+ @timeout = Integer(timeout)
11
+ end
12
+ end
13
+
14
+ class WaitRunner
15
+ def initialize(user_args:, render_command:, render_result:, io: nil, contents: nil)
16
+ @name = user_args.name
17
+ @wait = user_args.wait
18
+ @timeout_value = user_args.timeout
7
19
  @background = nil
8
20
  end
9
21
 
@@ -21,4 +33,4 @@ class Rundoc::CodeCommand::Background
21
33
  end
22
34
  end
23
35
  end
24
- Rundoc.register_code_command(:"background.wait", Rundoc::CodeCommand::Background::Wait)
36
+ Rundoc.register_code_command(keyword: :"background.wait", args_klass: Rundoc::CodeCommand::Background::WaitArgs, runner_klass: Rundoc::CodeCommand::Background::WaitRunner)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Rundoc::CodeCommand::Background
2
4
  end
3
5
 
@@ -1,31 +1,25 @@
1
- class Rundoc::CodeCommand::Bash
2
- # special purpose class to persist cd behavior across the entire program
3
- # we change the directory of the parent program (rundoc) rather than
4
- # changing the directory of a spawned child (via exec, ``, system, etc.)
5
- class Cd < Rundoc::CodeCommand::Bash
6
- def initialize(line)
1
+ # frozen_string_literal: true
2
+
3
+ class Rundoc::CodeCommand::BashRunner
4
+ class Cd < Rundoc::CodeCommand::BashRunner
5
+ def initialize(line, io: $stdout)
6
+ @io = io
7
7
  @line = line
8
8
  end
9
9
 
10
- # Ignore duplicate chdir warnings "warning: conflicting chdir during another chdir block"
11
- def supress_chdir_warning
12
- old_stderr = $stderr
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
- if old_stderr
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
- puts "running $ cd #{line}"
20
+ @io.puts "running $ cd #{line}"
27
21
 
28
- supress_chdir_warning do
22
+ suppress_chdir_warning do
29
23
  Dir.chdir(line)
30
24
  end
31
25
 
@@ -1,18 +1,32 @@
1
- class Rundoc::CodeCommand::Bash < Rundoc::CodeCommand
2
- # line = "cd ..""
3
- # line = "pwd"
4
- # line = "ls"
1
+ # frozen_string_literal: true
2
+
3
+ class Rundoc::CodeCommand::BashArgs
4
+ attr_reader :line
5
+
5
6
  def initialize(line)
6
7
  @line = line
7
- @contents = ""
8
+ end
9
+ end
10
+
11
+ class Rundoc::CodeCommand::BashRunner
12
+ attr_reader :io, :contents
13
+
14
+ def initialize(user_args:, render_command:, render_result:, io:, contents: nil)
15
+ @io = io
16
+ @contents = contents.dup if contents && !contents.empty?
17
+ @line = user_args.line
8
18
  @delegate = case @line.split(" ").first.downcase
9
19
  when "cd"
10
- Cd.new(@line)
20
+ Cd.new(@line, io: io)
11
21
  else
12
22
  false
13
23
  end
14
24
  end
15
25
 
26
+ def raise_on_error?
27
+ true
28
+ end
29
+
16
30
  def to_md(env = {})
17
31
  return @delegate.to_md(env) if @delegate
18
32
 
@@ -34,30 +48,40 @@ class Rundoc::CodeCommand::Bash < Rundoc::CodeCommand
34
48
  cmd = "(#{cmd}) 2>&1"
35
49
  msg = "Running: $ '#{cmd}'"
36
50
  msg << " with stdin: '#{stdin.inspect}'" if stdin && !stdin.empty?
37
- puts msg
51
+ io.puts msg
38
52
 
39
- result = ""
40
- IO.popen(cmd, "w+") do |io|
41
- io << stdin if stdin
42
- io.close_write
53
+ result = +""
54
+ IO.popen(cmd, "w+") do |pipe|
55
+ pipe << stdin if stdin
56
+ pipe.close_write
43
57
 
44
- until io.eof?
45
- buffer = io.gets
46
- puts " #{buffer}"
58
+ until pipe.eof?
59
+ buffer = pipe.gets
60
+ io.puts " #{buffer}"
47
61
 
48
62
  result << sanitize_escape_chars(buffer)
49
63
  end
50
64
  end
51
65
 
52
- unless $?.success?
53
- raise "Command `#{@line}` exited with non zero status: #{result}" unless keyword.to_s.include?("fail")
66
+ if raise_on_error? && !$?.success?
67
+ raise "Command `#{@line}` exited with non zero status: #{result}"
54
68
  end
55
69
  result
56
70
  end
57
71
  end
58
72
 
59
- Rundoc.register_code_command(:bash, Rundoc::CodeCommand::Bash)
60
- Rundoc.register_code_command(:"$", Rundoc::CodeCommand::Bash)
61
- Rundoc.register_code_command(:"fail.$", Rundoc::CodeCommand::Bash)
73
+ class Rundoc::CodeCommand::BashRunnerFailOk < Rundoc::CodeCommand::BashRunner
74
+ def raise_on_error?
75
+ false
76
+ end
77
+ end
78
+
79
+ Rundoc.register_code_command(keyword: :bash, args_klass: Rundoc::CodeCommand::BashArgs, runner_klass: Rundoc::CodeCommand::BashRunner)
80
+ Rundoc.register_code_command(keyword: :"$", args_klass: Rundoc::CodeCommand::BashArgs, runner_klass: Rundoc::CodeCommand::BashRunner)
81
+ Rundoc.register_code_command(
82
+ keyword: :"fail.$",
83
+ args_klass: Rundoc::CodeCommand::BashArgs,
84
+ runner_klass: Rundoc::CodeCommand::BashRunnerFailOk
85
+ )
62
86
 
63
87
  require "rundoc/code_command/bash/cd"
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Rundoc::CodeCommand::CommentArgs
4
+ attr_reader :line
5
+
6
+ def initialize(line = nil)
7
+ @line = line
8
+ end
9
+ end
10
+
11
+ class Rundoc::CodeCommand::CommentRunner
12
+ def initialize(user_args:, render_command:, render_result:, io:, contents: nil)
13
+ @io = io
14
+ @line = user_args&.line
15
+ @contents = contents
16
+ end
17
+
18
+ def call(env = {})
19
+ @io.puts "Skipping command (commented out): # #{@line}\n#{@contents}".strip
20
+ ""
21
+ end
22
+
23
+ def to_md(env = {})
24
+ ""
25
+ end
26
+ end
27
+
28
+ Rundoc.register_code_command(
29
+ keyword: :"#",
30
+ args_klass: Rundoc::CodeCommand::CommentArgs,
31
+ runner_klass: Rundoc::CodeCommand::CommentRunner,
32
+ always_hidden: true
33
+ )