rundoc 4.1.3 → 5.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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -1
  3. data/.standard.yml +1 -1
  4. data/CHANGELOG.md +12 -0
  5. data/README.md +36 -9
  6. data/lib/rundoc/cli.rb +6 -3
  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 +9 -5
  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 +7 -7
  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/file_command/append.rb +29 -8
  20. data/lib/rundoc/code_command/file_command/remove.rb +27 -5
  21. data/lib/rundoc/code_command/no_such_command.rb +8 -3
  22. data/lib/rundoc/code_command/pipe.rb +36 -16
  23. data/lib/rundoc/code_command/pre/erb.rb +28 -18
  24. data/lib/rundoc/code_command/print/erb.rb +28 -4
  25. data/lib/rundoc/code_command/print/text.rb +27 -8
  26. data/lib/rundoc/code_command/raw.rb +17 -5
  27. data/lib/rundoc/code_command/rundoc/require.rb +25 -17
  28. data/lib/rundoc/code_command/rundoc_command.rb +21 -8
  29. data/lib/rundoc/code_command/website/driver.rb +25 -7
  30. data/lib/rundoc/code_command/website/navigate.rb +18 -12
  31. data/lib/rundoc/code_command/website/screenshot.rb +17 -11
  32. data/lib/rundoc/code_command/website/visit.rb +26 -14
  33. data/lib/rundoc/code_command/website.rb +2 -0
  34. data/lib/rundoc/code_command/write.rb +37 -9
  35. data/lib/rundoc/code_command.rb +5 -48
  36. data/lib/rundoc/context/after_build.rb +2 -0
  37. data/lib/rundoc/context/execution.rb +2 -0
  38. data/lib/rundoc/document.rb +6 -2
  39. data/lib/rundoc/fenced_code_block.rb +10 -7
  40. data/lib/rundoc/peg_parser.rb +17 -9
  41. data/lib/rundoc/version.rb +3 -1
  42. data/lib/rundoc.rb +52 -17
  43. data/rundoc.gemspec +2 -0
  44. data/test/integration/background_stdin_test.rb +65 -15
  45. data/test/integration/website_test.rb +19 -0
  46. data/test/rundoc/code_commands/append_file_test.rb +35 -10
  47. data/test/rundoc/code_commands/background_test.rb +26 -22
  48. data/test/rundoc/code_commands/bash_test.rb +10 -5
  49. data/test/rundoc/code_commands/comment_test.rb +116 -0
  50. data/test/rundoc/code_commands/pipe_test.rb +2 -2
  51. data/test/rundoc/code_commands/print_test.rb +13 -25
  52. data/test/rundoc/code_commands/remove_contents_test.rb +8 -3
  53. data/test/rundoc/code_section_test.rb +28 -21
  54. data/test/rundoc/peg_parser_test.rb +17 -1
  55. data/test/test_helper.rb +32 -2
  56. metadata +7 -10
  57. data/lib/rundoc/code_command/rundoc/depend_on.rb +0 -13
  58. data/test/fixtures/depend_on/dependency/rundoc.md +0 -5
  59. 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: b65a04eca7d7099223b2a626c47eb78e14a8e102266cf880cddc747a0d9fc4a5
4
- data.tar.gz: 840585e8f0c6b3cddbb1d15354f4fdf862d2440b20054cd1b5344c026adb23d9
3
+ metadata.gz: 85fbfa9f84b4e98eb72d487721e7f35001f7820404e30a138de4b4505d788f53
4
+ data.tar.gz: 1ff08ba1cdd468b190fc54eef124b7ab434d700ffe87ba2b4c8e676d03fb515e
5
5
  SHA512:
6
- metadata.gz: 99661d14543918c21a988bd1c4cffc1d5c9d73a852a95c16b0905b5ddde312dc986d970b4082921752cd2eac4d786a0ae37f42f3577fdea2df54c6de50376810
7
- data.tar.gz: 6819d3babc4cb2a7ad1f76386e3d7e7f28886a989e6a31ef052ea87b3b226a6979cc1c2092653fa7aadadd34d3f8af9b268464c2a14c70f385b559ee8a13ca9a
6
+ metadata.gz: 8e87c610bcd8c8589407e68b503eee0578a1dc23b959baa138258185f7d08e6daa3ee17097e4b66b02fea896ee9e5be2bde936e2a601a2b18d1b1a2fc24b36a2
7
+ data.tar.gz: 9c9f4d7446beaedc74d61354794327cd30f372f7e5e16500678247f7bc549bb7b04992d0875c06c1fa06ae894a36c35902e07930cf55ab4e9fc01bb8b29c44d3
@@ -11,9 +11,9 @@ jobs:
11
11
  fail-fast: false
12
12
  matrix:
13
13
  ruby:
14
- - 3.1
15
14
  - 3.2
16
15
  - 3.3
16
+ - "4.0"
17
17
  - head
18
18
  steps:
19
19
  - name: Checkout code
@@ -34,6 +34,7 @@ jobs:
34
34
  matrix:
35
35
  ruby:
36
36
  - 3.3
37
+ - "4.0"
37
38
  - head
38
39
  steps:
39
40
  - 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
+ ## 5.0.0
4
+
5
+ - Added comment syntax. Use an octothorpe (`#`) after the visibility markers to comment out any commands and make them a no-op.
6
+ - Changed: Minimum Ruby version is now 3.2 (Ruby 3.1 reached EOL in March 2025).
7
+ - Changed: All code commands now use an Args + Runner class pattern. `register_code_command` now requires keyword arguments: `keyword:`, `args_klass:`, and `runner_klass:`.
8
+ - 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)
9
+
10
+ ## 4.1.4
11
+
12
+ - Fix: Net::ReadTimeout errors on `website.visit` are now retried by default (https://github.com/zombocom/rundoc/pull/103)
13
+ - Fix: When a background process (`background.start`) does not exit cleanly, print its logs for help with debugging. (https://github.com/zombocom/rundoc/pull/104)
14
+
3
15
  ## 4.1.3
4
16
 
5
17
  - Fix: Internal error in `background.wait` introduced in 4.1.2 (https://github.com/zombocom/rundoc/pull/97)
data/README.md CHANGED
@@ -660,36 +660,63 @@ This command `filter_sensitive` can be called multiple times with different valu
660
660
 
661
661
  ## Writing a new command
662
662
 
663
+ > Note: This is an advanced topic and this interface is unstable.
664
+
663
665
  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
666
 
665
667
  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
668
 
667
- A new command needs to be registered:
669
+ 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
670
 
671
+ ```ruby
672
+ Rundoc.register_code_command(
673
+ keyword: :lol,
674
+ args_klass: Rundoc::CodeCommand::LolArgs,
675
+ runner_klass: Rundoc::CodeCommand::LolRunner
676
+ )
669
677
  ```
670
- Rundoc.register_code_command(:lol, Rundoc::CodeCommand::Lol)
671
- ```
672
678
 
673
- They should inherit from Rundoc::CodeCommand:
679
+ The Args class is a plain Ruby class. Its initialize method receives input from the document and exposes parsed values via `attr_reader`:
680
+
681
+ ```ruby
682
+ class Rundoc::CodeCommand::LolArgs
683
+ attr_reader :message
674
684
 
685
+ def initialize(message)
686
+ @message = message
687
+ end
688
+ end
675
689
  ```
676
- class Rundoc::CodeCommand::Lol < Rundoc::CodeCommand
677
- def initialize(line)
690
+
691
+ The Runner class is namespaced under `Rundoc::CodeCommand` and receives the args instance via `user_args:`:
692
+
693
+ ```ruby
694
+ class Rundoc::CodeCommand::LolRunner
695
+ def initialize(user_args:, **)
696
+ @message = user_args.message
697
+ end
698
+
699
+ def to_md(env = {})
700
+ ""
701
+ end
702
+
703
+ def call(env = {})
704
+ @message
678
705
  end
679
706
  end
680
707
  ```
681
708
 
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.
709
+ 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
710
 
684
711
  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
712
 
686
- Command initialize methods natively support:
713
+ Args class initialize methods natively support:
687
714
 
688
715
  - Barewords as a single string input
689
716
  - Keyword arguments
690
717
  - A combination of the two
691
718
 
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.
719
+ 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
720
 
694
721
  ## Copyright
695
722
 
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
@@ -159,7 +161,8 @@ module Rundoc
159
161
  Dir.chdir(execution_context.output_dir) do
160
162
  parser = Rundoc::Document.new(
161
163
  source_contents,
162
- context: execution_context
164
+ context: execution_context,
165
+ io: io
163
166
  )
164
167
  output = begin
165
168
  parser.to_md
@@ -176,8 +179,8 @@ module Rundoc
176
179
  Rundoc::CodeCommand::Background::ProcessSpawn.tasks.each do |name, task|
177
180
  next unless task.alive?
178
181
 
179
- io.puts "Warning background task is still running, cleaning up: name: #{name}"
180
- task.stop
182
+ io.puts "Warning background task is still running, cleaning up: `#{name}`"
183
+ task.stop(print_io: io)
181
184
  end
182
185
 
183
186
  if execution_context.output_dir.exist?
@@ -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"
@@ -45,16 +47,16 @@ class Rundoc::CodeCommand::Background
45
47
  attr_reader :log, :pid, :command
46
48
 
47
49
  def initialize(command, timeout: 5, log: Tempfile.new("log"), out: "2>&1")
48
- @command = command
50
+ @original_command = command
49
51
  @timeout_value = timeout
50
- @log_reference = log # https://twitter.com/schneems/status/1285289971083907075
52
+ @log_reference = log # Need to keep a reference to `Tempfile` or it will be deleted. Pathname does not retain the passed in reference
51
53
 
52
54
  @log = Pathname.new(log)
53
55
  @log.dirname.mkpath
54
56
  FileUtils.touch(@log)
55
57
  @pipe_output, @pipe_input = IO.pipe
56
58
 
57
- @command = "/usr/bin/env bash -c #{@command.shellescape} >> #{@log} #{out}"
59
+ @command = "/usr/bin/env bash -c #{@original_command.shellescape} >> #{@log} #{out}"
58
60
  @pid = nil
59
61
  end
60
62
 
@@ -120,13 +122,15 @@ class Rundoc::CodeCommand::Background
120
122
  contents
121
123
  end
122
124
 
123
- def stop
125
+ def stop(print_io: nil)
124
126
  return unless alive?
125
127
  @pipe_input.close
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}"
132
+ ensure
133
+ print_io&.puts "Log contents for `#{command}`:\n#{@log.read}"
130
134
  end
131
135
 
132
136
  def check_alive!
@@ -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,9 +1,9 @@
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
 
@@ -23,7 +23,7 @@ class Rundoc::CodeCommand::Bash
23
23
 
24
24
  def call(env)
25
25
  line = @line.sub("cd", "").strip
26
- puts "running $ cd #{line}"
26
+ @io.puts "running $ cd #{line}"
27
27
 
28
28
  supress_chdir_warning do
29
29
  Dir.chdir(line)
@@ -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
+ )
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rundoc
4
+ module CodeCommand
5
+ # Hold enough information to construct commands, but don't yet
6
+ #
7
+ # Allows us to separate parse time constructs from runtime injectables
8
+ # (such as IO). Which gives us a cleaner running model.
9
+ class Deferred
10
+ attr_accessor :render_result, :render_command,
11
+ :contents, :keyword, :original_args
12
+
13
+ alias_method :render_result?, :render_result
14
+ alias_method :render_command?, :render_command
15
+
16
+ attr_reader :runner_klass
17
+
18
+ def initialize(args_instance:, runner_klass:, always_hidden: false)
19
+ @args_instance = args_instance
20
+ @runner_klass = runner_klass
21
+ @always_hidden = always_hidden
22
+ end
23
+
24
+ def hidden?
25
+ !render_command? && !render_result?
26
+ end
27
+
28
+ def not_hidden?
29
+ !hidden?
30
+ end
31
+
32
+ def push(contents)
33
+ @contents ||= +""
34
+ @contents << contents
35
+ end
36
+ alias_method :<<, :push
37
+
38
+ def build(io: $stdout)
39
+ @built ||= begin
40
+ runner = @runner_klass.new(
41
+ user_args: @args_instance,
42
+ render_command: render_command,
43
+ render_result: render_result,
44
+ contents: @contents,
45
+ io: io
46
+ )
47
+ if @always_hidden
48
+ @render_command = false
49
+ @render_result = false
50
+ end
51
+ runner
52
+ end
53
+ rescue UnknownCommand
54
+ raise "No such command registered with rundoc #{keyword.inspect} for `#{keyword} #{original_args}`"
55
+ end
56
+
57
+ def call(env = {})
58
+ build.call(env)
59
+ end
60
+
61
+ def to_md(env = {})
62
+ build.to_md(env)
63
+ end
64
+ end
65
+ end
66
+ end