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
@@ -1,18 +1,39 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Rundoc::CodeCommand::FileCommand
2
- class Append < Rundoc::CodeCommand
3
- include FileUtil
4
+ class AppendArgs
5
+ attr_reader :filename
4
6
 
5
7
  def initialize(filename)
6
- @filename, line = filename.split("#")
8
+ @filename = filename
9
+ end
10
+ end
11
+
12
+ class AppendRunner
13
+ NEWLINE = Rundoc::CodeCommand::WriteRunner::NEWLINE
14
+
15
+ include Rundoc::CodeCommand::FileUtil
16
+
17
+ attr_reader :io, :contents
18
+
19
+ def initialize(user_args:, render_command:, render_result:, io:, contents: nil)
20
+ @filename, line = user_args.filename.split("#")
7
21
  @line_number = if line
8
22
  Integer(line)
9
23
  end
24
+ @io = io
25
+ @render_command = render_command
26
+ @contents = contents.dup if contents && !contents.empty?
27
+ end
28
+
29
+ def render_command?
30
+ @render_command
10
31
  end
11
32
 
12
33
  def to_md(env)
13
34
  return unless render_command?
14
35
 
15
- if env[:commands].any? { |c| c[:object].not_hidden? }
36
+ if env[:commands].any? { |c| c[:visibility].not_hidden? }
16
37
  raise "Must call append in its own code section"
17
38
  end
18
39
 
@@ -34,7 +55,7 @@ class Rundoc::CodeCommand::FileCommand
34
55
  end
35
56
 
36
57
  def concat_with_newline(str1, str2)
37
- result = ""
58
+ result = +""
38
59
  result << str1
39
60
  result << "\n" unless ends_in_newline?(result)
40
61
  result << str2
@@ -61,10 +82,10 @@ class Rundoc::CodeCommand::FileCommand
61
82
  mkdir_p
62
83
  doc = File.read(filename)
63
84
  if @line_number
64
- puts "Writing to: '#{filename}' line #{@line_number} with: #{contents.inspect}"
85
+ io.puts "Writing to: '#{filename}' line #{@line_number} with: #{contents.inspect}"
65
86
  doc = insert_contents_into_at_line(doc)
66
87
  else
67
- puts "Appending to file: '#{filename}' with: #{contents.inspect}"
88
+ io.puts "Appending to file: '#{filename}' with: #{contents.inspect}"
68
89
  doc = concat_with_newline(doc, contents)
69
90
  end
70
91
 
@@ -74,4 +95,4 @@ class Rundoc::CodeCommand::FileCommand
74
95
  end
75
96
  end
76
97
 
77
- Rundoc.register_code_command(:"file.append", Rundoc::CodeCommand::FileCommand::Append)
98
+ Rundoc.register_code_command(keyword: :"file.append", args_klass: Rundoc::CodeCommand::FileCommand::AppendArgs, runner_klass: Rundoc::CodeCommand::FileCommand::AppendRunner)
@@ -1,13 +1,35 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Rundoc::CodeCommand::FileCommand
2
- class Remove < Rundoc::CodeCommand
3
- include FileUtil
4
+ class RemoveArgs
5
+ attr_reader :filename
4
6
 
5
7
  def initialize(filename)
6
8
  @filename = filename
7
9
  end
10
+ end
11
+
12
+ class RemoveRunner
13
+ NEWLINE = Object.new
14
+ def NEWLINE.to_s
15
+ ""
16
+ end
17
+
18
+ def NEWLINE.empty?
19
+ false
20
+ end
21
+ include Rundoc::CodeCommand::FileUtil
22
+
23
+ attr_reader :io, :contents
24
+
25
+ def initialize(user_args:, render_command:, render_result:, io:, contents: nil)
26
+ @filename = user_args.filename
27
+ @io = io
28
+ @contents = contents.dup if contents && !contents.empty?
29
+ end
8
30
 
9
31
  def to_md(env)
10
- if env[:commands].any? { |c| c[:object].not_hidden? }
32
+ if env[:commands].any? { |c| c[:visibility].not_hidden? }
11
33
  raise "Must call remove in its own code section"
12
34
  end
13
35
 
@@ -17,7 +39,7 @@ class Rundoc::CodeCommand::FileCommand
17
39
  end
18
40
 
19
41
  def call(env = {})
20
- puts "Deleting '#{contents.strip}' from #{filename}"
42
+ io.puts "Deleting '#{contents.strip}' from #{filename}"
21
43
  raise "#{filename} does not exist" unless File.exist?(filename)
22
44
 
23
45
  regex = /^\s*#{Regexp.quote(contents)}/
@@ -30,4 +52,4 @@ class Rundoc::CodeCommand::FileCommand
30
52
  end
31
53
  end
32
54
 
33
- Rundoc.register_code_command(:"file.remove", Rundoc::CodeCommand::FileCommand::Remove)
55
+ Rundoc.register_code_command(keyword: :"file.remove", args_klass: Rundoc::CodeCommand::FileCommand::RemoveArgs, runner_klass: Rundoc::CodeCommand::FileCommand::RemoveRunner)
@@ -1,8 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rundoc
2
- class CodeCommand
3
- class NoSuchCommand < Rundoc::CodeCommand
4
+ module CodeCommand
5
+ class NoSuchCommand
6
+ def initialize(user_args: nil, render_command: false, render_result: false, io: nil, contents: nil)
7
+ end
8
+
4
9
  def call(env = {})
5
- raise "No such command registered with rundoc: #{@keyword.inspect} for '#{@keyword} #{@original_args}'"
10
+ raise UnknownCommand
6
11
  end
7
12
  end
8
13
  end
@@ -1,11 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rundoc
2
- class CodeCommand
3
- class Pipe < Rundoc::CodeCommand
4
- # ::: ls
5
- # ::: | tail -n 2
6
- # => "test\ntmp.file\n"
4
+ module CodeCommand
5
+ class PipeArgs
6
+ attr_reader :line
7
+
7
8
  def initialize(line)
8
- @delegate = parse(line)
9
+ @line = line
10
+ end
11
+ end
12
+
13
+ class PipeRunner
14
+ attr_reader :io
15
+
16
+ def initialize(user_args:, render_command:, render_result:, io:, contents: nil)
17
+ @io = io
18
+ @delegate = parse(user_args.line)
9
19
  end
10
20
 
11
21
  # before: "",
@@ -14,10 +24,10 @@ module Rundoc
14
24
  # [[cmd, output], [cmd, output]]
15
25
  def call(env = {})
16
26
  last_command = env[:commands].last
17
- puts "Piping: results of '#{last_command[:command]}' to '#{@delegate}'"
27
+ io.puts "Piping: results of '#{last_command[:command]}' to '#{@delegate}'"
18
28
 
19
29
  @delegate.push(last_command[:output])
20
- @delegate.call(env)
30
+ @delegate.build(io: io).call(env)
21
31
  end
22
32
 
23
33
  def to_md(env = {})
@@ -31,17 +41,27 @@ module Rundoc
31
41
 
32
42
  actual = actual.first if actual.is_a?(Array)
33
43
 
34
- actual = Rundoc::CodeCommand::Bash.new(code) if actual.is_a?(Rundoc::CodeCommand::NoSuchCommand)
35
- actual
36
-
37
- # Since `| tail -n 2` does not start with a `$` assume any "naked" commands
38
- # are bash
44
+ if actual.runner_klass == Rundoc::CodeCommand::NoSuchCommand
45
+ bash_deferred(code)
46
+ else
47
+ actual
48
+ end
39
49
  rescue Parslet::ParseFailed
40
- Rundoc::CodeCommand::Bash.new(code)
50
+ bash_deferred(code)
51
+ end
52
+
53
+ private def bash_deferred(code)
54
+ deferred = Rundoc::CodeCommand::Deferred.new(
55
+ args_instance: Rundoc::CodeCommand::BashArgs.new(code),
56
+ runner_klass: Rundoc::CodeCommand::BashRunner
57
+ )
58
+ deferred.render_command = false
59
+ deferred.render_result = false
60
+ deferred
41
61
  end
42
62
  end
43
63
  end
44
64
  end
45
65
 
46
- Rundoc.register_code_command(:pipe, Rundoc::CodeCommand::Pipe)
47
- Rundoc.register_code_command(:|, Rundoc::CodeCommand::Pipe)
66
+ Rundoc.register_code_command(keyword: :pipe, args_klass: Rundoc::CodeCommand::PipeArgs, runner_klass: Rundoc::CodeCommand::PipeRunner)
67
+ Rundoc.register_code_command(keyword: :|, args_klass: Rundoc::CodeCommand::PipeArgs, runner_klass: Rundoc::CodeCommand::PipeRunner)
@@ -1,44 +1,54 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "../print/erb"
2
4
 
3
- class Rundoc::CodeCommand
4
- class PreErb < Rundoc::CodeCommand
5
+ module Rundoc::CodeCommand
6
+ class PreErbArgs
7
+ attr_reader :line
8
+
5
9
  def initialize(line)
6
10
  @line = line
11
+ end
12
+ end
13
+
14
+ class PreErbRunner
15
+ attr_reader :io, :contents
16
+
17
+ def initialize(user_args:, render_command:, render_result:, io:, contents: nil)
18
+ @line = user_args.line
7
19
  @binding = RUNDOC_ERB_BINDINGS[RUNDOC_DEFAULT_ERB_BINDING]
8
20
  @code = nil
9
21
  @command = nil
10
22
  @template = nil
11
- @render_delegate_result = nil
12
- @render_delegate_command = nil
13
- # Hide self, pass visibility onto delegate
14
- @render_result = false
15
- @render_command = false
23
+ @render_command = render_command
24
+ @render_result = render_result
25
+ @io = io
26
+ @contents = contents.dup if contents && !contents.empty?
16
27
  end
17
28
 
18
- # Visibility is injected by the parser, capture it and pass it to the delegate
19
- def render_result=(value)
20
- @render_delegate_result = value
29
+ def render_command?
30
+ @render_command
21
31
  end
22
32
 
23
- def render_command=(value)
24
- @render_delegate_command = value
33
+ def render_result?
34
+ @render_result
25
35
  end
26
36
 
27
37
  def code
28
38
  @code ||= begin
29
39
  vis = +""
30
- vis += @render_delegate_command ? ">" : "-"
31
- vis += @render_delegate_result ? ">" : "-"
40
+ vis += render_command? ? ">" : "-"
41
+ vis += render_result? ? ">" : "-"
32
42
  code = [@line, @contents]
33
43
  .compact
34
44
  .reject(&:empty?)
35
45
  .join("\n")
36
46
  @template = ":::#{vis} #{code}"
37
47
 
38
- puts "pre.erb: Applying ERB, template:\n#{@template}"
48
+ io.puts "pre.erb: Applying ERB, template:\n#{@template}"
39
49
  result = ERB.new(@template).result(@binding)
40
- puts "pre.erb: ERB result:\n#{result}"
41
- puts "pre.erb: done, ready to delegate"
50
+ io.puts "pre.erb: ERB result:\n#{result}"
51
+ io.puts "pre.erb: done, ready to delegate"
42
52
  result
43
53
  end
44
54
  end
@@ -60,4 +70,4 @@ class Rundoc::CodeCommand
60
70
  end
61
71
  end
62
72
  end
63
- Rundoc.register_code_command(:"pre.erb", Rundoc::CodeCommand::PreErb)
73
+ Rundoc.register_code_command(keyword: :"pre.erb", args_klass: Rundoc::CodeCommand::PreErbArgs, runner_klass: Rundoc::CodeCommand::PreErbRunner, always_hidden: true)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "erb"
2
4
 
3
5
  class EmptyBinding
@@ -10,14 +12,36 @@ class EmptyBinding
10
12
  end
11
13
  end
12
14
 
13
- class Rundoc::CodeCommand
15
+ module Rundoc::CodeCommand
14
16
  RUNDOC_ERB_BINDINGS = Hash.new { |h, k| h[k] = EmptyBinding.create }
15
17
  RUNDOC_DEFAULT_ERB_BINDING = "default"
16
18
 
17
- class PrintERB < Rundoc::CodeCommand
19
+ class PrintERBArgs
20
+ attr_reader :line, :binding_name
21
+
18
22
  def initialize(line = nil, binding: RUNDOC_DEFAULT_ERB_BINDING)
19
23
  @line = line
20
- @binding = RUNDOC_ERB_BINDINGS[binding]
24
+ @binding_name = binding
25
+ end
26
+ end
27
+
28
+ class PrintERBRunner
29
+ attr_reader :contents
30
+
31
+ def initialize(user_args:, render_command:, render_result:, io: nil, contents: nil)
32
+ @line = user_args.line
33
+ @binding = RUNDOC_ERB_BINDINGS[user_args.binding_name]
34
+ @render_command = render_command
35
+ @render_result = render_result
36
+ @contents = contents.dup if contents && !contents.empty?
37
+ end
38
+
39
+ def render_command?
40
+ @render_command
41
+ end
42
+
43
+ def render_result?
44
+ @render_result
21
45
  end
22
46
 
23
47
  def to_md(env)
@@ -45,4 +69,4 @@ class Rundoc::CodeCommand
45
69
  end
46
70
  end
47
71
  end
48
- Rundoc.register_code_command(:"print.erb", Rundoc::CodeCommand::PrintERB)
72
+ Rundoc.register_code_command(keyword: :"print.erb", args_klass: Rundoc::CodeCommand::PrintERBArgs, runner_klass: Rundoc::CodeCommand::PrintERBRunner)
@@ -1,8 +1,31 @@
1
- class Rundoc::CodeCommand
2
- class PrintText < Rundoc::CodeCommand
3
- def initialize(line)
1
+ # frozen_string_literal: true
2
+
3
+ module Rundoc::CodeCommand
4
+ class PrintTextArgs
5
+ attr_reader :line
6
+
7
+ def initialize(line = nil)
4
8
  @line = line
5
9
  end
10
+ end
11
+
12
+ class PrintTextRunner
13
+ attr_reader :contents
14
+
15
+ def initialize(user_args:, render_command:, render_result:, io: nil, contents: nil)
16
+ @line = user_args.line
17
+ @render_command = render_command
18
+ @render_result = render_result
19
+ @contents = contents.dup if contents && !contents.empty?
20
+ end
21
+
22
+ def render_command?
23
+ @render_command
24
+ end
25
+
26
+ def render_result?
27
+ @render_result
28
+ end
6
29
 
7
30
  def to_md(env)
8
31
  if render_before?
@@ -12,10 +35,6 @@ class Rundoc::CodeCommand
12
35
  ""
13
36
  end
14
37
 
15
- def hidden?
16
- !render_result?
17
- end
18
-
19
38
  def call(env = {})
20
39
  if render_before?
21
40
  ""
@@ -30,4 +49,4 @@ class Rundoc::CodeCommand
30
49
  end
31
50
  end
32
51
 
33
- Rundoc.register_code_command(:"print.text", Rundoc::CodeCommand::PrintText)
52
+ Rundoc.register_code_command(keyword: :"print.text", args_klass: Rundoc::CodeCommand::PrintTextArgs, runner_klass: Rundoc::CodeCommand::PrintTextRunner)
@@ -1,9 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rundoc
2
- class CodeCommand
3
- class Raw < CodeCommand
4
- def initialize(contents, visible: true)
5
- @contents = contents
6
- @render_result = visible
4
+ module CodeCommand
5
+ # Wraps lines inside a fenced code block that are not rundoc commands.
6
+ # These are rendered as-is without executing any code.
7
+ #
8
+ # Example:
9
+ #
10
+ # ```ruby
11
+ # gem 'sqlite3' <- parsed as Raw
12
+ # :::>> $ echo "hi" <- parsed as a code command
13
+ # ```
14
+ class Raw
15
+ attr_reader :contents
16
+
17
+ def initialize(user_args: nil, render_command: true, render_result: true, io: nil, contents: nil)
18
+ @contents = contents.dup if contents && !contents.empty?
7
19
  end
8
20
 
9
21
  def call(env = {})
@@ -1,13 +1,28 @@
1
- class ::Rundoc::CodeCommand
1
+ # frozen_string_literal: true
2
+
3
+ module ::Rundoc::CodeCommand
2
4
  class RundocCommand
3
- class Require < ::Rundoc::CodeCommand
4
- # Pass in the relative path of another rundoc document in order to
5
- # run all of it's commands. Resulting contents will be displayed
6
- # in current document
5
+ class RequireArgs
6
+ attr_reader :path
7
+
7
8
  def initialize(path)
8
9
  raise "Path must be relative (i.e. start with `.` or `..`. #{path.inspect} does not" unless path.start_with?(".")
9
10
  @path = Pathname.new(path)
10
11
  end
12
+ end
13
+
14
+ class RequireRunner
15
+ attr_reader :io
16
+
17
+ def initialize(user_args:, render_command:, render_result:, io:, contents: nil)
18
+ @path = user_args.path
19
+ @io = io
20
+ @render_result = render_result
21
+ end
22
+
23
+ def render_result?
24
+ @render_result
25
+ end
11
26
 
12
27
  def to_md(env = {})
13
28
  ""
@@ -24,28 +39,21 @@ class ::Rundoc::CodeCommand
24
39
  output_dir: execution_context.output_dir,
25
40
  screenshots_dirname: execution_context.screenshots_dir,
26
41
  with_contents_dir: execution_context.with_contents_dir
27
- )
42
+ ),
43
+ io: io
28
44
  ).to_md
29
45
 
30
46
  if render_result?
31
- puts "rundoc.require: Done executing #{@path.to_s.inspect}, putting contents into document"
47
+ io.puts "rundoc.require: Done executing #{@path.to_s.inspect}, putting contents into document"
32
48
  env[:before] << output
33
49
  else
34
- puts "rundoc.require: Done executing #{@path.to_s.inspect}, quietly"
50
+ io.puts "rundoc.require: Done executing #{@path.to_s.inspect}, quietly"
35
51
  end
36
52
 
37
53
  ""
38
54
  end
39
-
40
- def hidden?
41
- !render_result?
42
- end
43
-
44
- def not_hidden?
45
- !hidden?
46
- end
47
55
  end
48
56
  end
49
57
  end
50
58
 
51
- Rundoc.register_code_command(:"rundoc.require", ::Rundoc::CodeCommand::RundocCommand::Require)
59
+ Rundoc.register_code_command(keyword: :"rundoc.require", args_klass: ::Rundoc::CodeCommand::RundocCommand::RequireArgs, runner_klass: ::Rundoc::CodeCommand::RundocCommand::RequireRunner)
@@ -1,8 +1,22 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ::Rundoc
2
- class CodeCommand
3
- class ::RundocCommand < ::Rundoc::CodeCommand
4
- def initialize(contents = "")
5
- @contents = contents
4
+ module CodeCommand
5
+ class RundocCommandArgs
6
+ attr_reader :code
7
+
8
+ def initialize(code = "")
9
+ @code = code
10
+ end
11
+ end
12
+
13
+ class RundocCommandRunner
14
+ attr_reader :io, :contents
15
+
16
+ def initialize(user_args:, render_command:, render_result:, io:, contents: nil)
17
+ @io = io
18
+ @contents = contents.dup if contents && !contents.empty?
19
+ @contents = user_args.code + (@contents || +"")
6
20
  end
7
21
 
8
22
  def to_md(env = {})
@@ -10,7 +24,7 @@ module ::Rundoc
10
24
  end
11
25
 
12
26
  def call(env = {})
13
- puts "Running: #{contents}"
27
+ io.puts "Running: #{contents}"
14
28
  eval(contents) # rubocop:disable Security/Eval
15
29
  ""
16
30
  end
@@ -18,8 +32,7 @@ module ::Rundoc
18
32
  end
19
33
  end
20
34
 
21
- Rundoc.register_code_command(:rundoc, RundocCommand)
22
- Rundoc.register_code_command(:"rundoc.configure", RundocCommand)
35
+ Rundoc.register_code_command(keyword: :rundoc, args_klass: Rundoc::CodeCommand::RundocCommandArgs, runner_klass: Rundoc::CodeCommand::RundocCommandRunner)
36
+ Rundoc.register_code_command(keyword: :"rundoc.configure", args_klass: Rundoc::CodeCommand::RundocCommandArgs, runner_klass: Rundoc::CodeCommand::RundocCommandRunner)
23
37
 
24
- require "rundoc/code_command/rundoc/depend_on"
25
38
  require "rundoc/code_command/rundoc/require"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "capybara"
2
4
 
3
5
  Capybara::Selenium::Driver.load_selenium
@@ -6,7 +8,8 @@ class Rundoc::CodeCommand::Website
6
8
  class Driver
7
9
  attr_reader :session
8
10
 
9
- def initialize(name:, url:, width: 1024, height: 720, visible: false)
11
+ def initialize(name:, url:, width: 1024, height: 720, visible: false, io: $stdout, read_timeout: 60)
12
+ @io = io
10
13
  browser_options = ::Selenium::WebDriver::Chrome::Options.new
11
14
  browser_options.args << "--headless" unless visible
12
15
  browser_options.args << "--disable-gpu" if Gem.win_platform?
@@ -15,7 +18,10 @@ class Rundoc::CodeCommand::Website
15
18
  @width = width
16
19
  @height = height
17
20
 
18
- @driver = Capybara::Selenium::Driver.new(nil, browser: :chrome, options: browser_options)
21
+ client = Selenium::WebDriver::Remote::Http::Default.new
22
+ client.read_timeout = read_timeout
23
+
24
+ @driver = Capybara::Selenium::Driver.new(nil, browser: :chrome, options: browser_options, http_client: client)
19
25
  driver_name = :"rundoc_driver_#{name}"
20
26
  Capybara.register_driver(driver_name) do |app|
21
27
  @driver
@@ -24,8 +30,20 @@ class Rundoc::CodeCommand::Website
24
30
  @session = Capybara::Session.new(driver_name)
25
31
  end
26
32
 
27
- def visit(url)
28
- @session.visit(url)
33
+ def visit(url, max_attempts: 3, delay: 1)
34
+ attempts = 0
35
+ begin
36
+ @session.visit(url)
37
+ rescue ::Net::ReadTimeout => e
38
+ attempts += 1
39
+ if attempts > max_attempts
40
+ raise e
41
+ else
42
+ @io.puts "Error visiting url (#{attempts}/#{max_attempts}) `#{url}`:\n#{e}"
43
+ sleep delay
44
+ retry
45
+ end
46
+ end
29
47
  end
30
48
 
31
49
  def timestamp
@@ -54,7 +72,7 @@ class Rundoc::CodeCommand::Website
54
72
  msg = +""
55
73
  msg << "Error running code #{code.inspect} at #{current_url.inspect}\n"
56
74
  msg << "saving a screenshot to: `tmp/error.png`"
57
- puts msg
75
+ @io.puts msg
58
76
  error_path = env[:context].screenshots_dir.join("error.png")
59
77
  session.save_screenshot(error_path)
60
78
  raise e
@@ -66,13 +84,13 @@ class Rundoc::CodeCommand::Website
66
84
  file_name = self.class.next_screenshot_name
67
85
  file_path = screenshots_dir.join(file_name)
68
86
  session.save_screenshot(file_path)
69
- puts "Screenshot saved to #{file_path}"
87
+ @io.puts "Screenshot saved to #{file_path}"
70
88
 
71
89
  return file_path unless upload
72
90
 
73
91
  case upload
74
92
  when "s3", "aws"
75
- puts "Uploading screenshot to S3"
93
+ @io.puts "Uploading screenshot to S3"
76
94
  require "aws-sdk-s3"
77
95
  ENV.fetch("AWS_ACCESS_KEY_ID")
78
96
  s3 = Aws::S3::Resource.new(region: ENV.fetch("AWS_REGION"))
@@ -1,8 +1,22 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Rundoc::CodeCommand::Website
2
- class Navigate < Rundoc::CodeCommand
4
+ class NavigateArgs
5
+ attr_reader :name
6
+
3
7
  def initialize(name:)
4
8
  @name = name
9
+ end
10
+ end
11
+
12
+ class NavigateRunner
13
+ attr_reader :io, :contents
14
+
15
+ def initialize(user_args:, render_command:, render_result:, io:, contents: nil)
16
+ @name = user_args.name
5
17
  @driver = nil
18
+ @io = io
19
+ @contents = contents.dup if contents && !contents.empty?
6
20
  end
7
21
 
8
22
  def driver
@@ -14,20 +28,12 @@ class Rundoc::CodeCommand::Website
14
28
  end
15
29
 
16
30
  def call(env = {})
17
- puts "website.navigate [#{@name}]: #{contents}"
31
+ io.puts "website.navigate [#{@name}]: #{contents}"
18
32
  driver.safe_eval(contents, env)
19
33
  ""
20
34
  end
21
-
22
- def hidden?
23
- true
24
- end
25
-
26
- def not_hidden?
27
- !hidden?
28
- end
29
35
  end
30
36
  end
31
37
 
32
- Rundoc.register_code_command(:"website.nav", Rundoc::CodeCommand::Website::Navigate)
33
- Rundoc.register_code_command(:"website.navigate", Rundoc::CodeCommand::Website::Navigate)
38
+ Rundoc.register_code_command(keyword: :"website.nav", args_klass: Rundoc::CodeCommand::Website::NavigateArgs, runner_klass: Rundoc::CodeCommand::Website::NavigateRunner)
39
+ Rundoc.register_code_command(keyword: :"website.navigate", args_klass: Rundoc::CodeCommand::Website::NavigateArgs, runner_klass: Rundoc::CodeCommand::Website::NavigateRunner)