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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +2 -1
- data/.standard.yml +1 -1
- data/CHANGELOG.md +12 -0
- data/README.md +36 -9
- data/lib/rundoc/cli.rb +6 -3
- data/lib/rundoc/code_command/background/log/clear.rb +12 -2
- data/lib/rundoc/code_command/background/log/read.rb +12 -2
- data/lib/rundoc/code_command/background/process_spawn.rb +9 -5
- data/lib/rundoc/code_command/background/start.rb +25 -6
- data/lib/rundoc/code_command/background/stdin_write.rb +21 -8
- data/lib/rundoc/code_command/background/stop.rb +12 -2
- data/lib/rundoc/code_command/background/wait.rb +15 -3
- data/lib/rundoc/code_command/background.rb +2 -0
- data/lib/rundoc/code_command/bash/cd.rb +7 -7
- data/lib/rundoc/code_command/bash.rb +43 -19
- data/lib/rundoc/code_command/comment.rb +33 -0
- data/lib/rundoc/code_command/deferred.rb +66 -0
- data/lib/rundoc/code_command/file_command/append.rb +29 -8
- data/lib/rundoc/code_command/file_command/remove.rb +27 -5
- data/lib/rundoc/code_command/no_such_command.rb +8 -3
- data/lib/rundoc/code_command/pipe.rb +36 -16
- data/lib/rundoc/code_command/pre/erb.rb +28 -18
- data/lib/rundoc/code_command/print/erb.rb +28 -4
- data/lib/rundoc/code_command/print/text.rb +27 -8
- data/lib/rundoc/code_command/raw.rb +17 -5
- data/lib/rundoc/code_command/rundoc/require.rb +25 -17
- data/lib/rundoc/code_command/rundoc_command.rb +21 -8
- data/lib/rundoc/code_command/website/driver.rb +25 -7
- data/lib/rundoc/code_command/website/navigate.rb +18 -12
- data/lib/rundoc/code_command/website/screenshot.rb +17 -11
- data/lib/rundoc/code_command/website/visit.rb +26 -14
- data/lib/rundoc/code_command/website.rb +2 -0
- data/lib/rundoc/code_command/write.rb +37 -9
- data/lib/rundoc/code_command.rb +5 -48
- data/lib/rundoc/context/after_build.rb +2 -0
- data/lib/rundoc/context/execution.rb +2 -0
- data/lib/rundoc/document.rb +6 -2
- data/lib/rundoc/fenced_code_block.rb +10 -7
- data/lib/rundoc/peg_parser.rb +17 -9
- data/lib/rundoc/version.rb +3 -1
- data/lib/rundoc.rb +52 -17
- data/rundoc.gemspec +2 -0
- data/test/integration/background_stdin_test.rb +65 -15
- data/test/integration/website_test.rb +19 -0
- data/test/rundoc/code_commands/append_file_test.rb +35 -10
- data/test/rundoc/code_commands/background_test.rb +26 -22
- data/test/rundoc/code_commands/bash_test.rb +10 -5
- data/test/rundoc/code_commands/comment_test.rb +116 -0
- data/test/rundoc/code_commands/pipe_test.rb +2 -2
- data/test/rundoc/code_commands/print_test.rb +13 -25
- data/test/rundoc/code_commands/remove_contents_test.rb +8 -3
- data/test/rundoc/code_section_test.rb +28 -21
- data/test/rundoc/peg_parser_test.rb +17 -1
- data/test/test_helper.rb +32 -2
- metadata +7 -10
- data/lib/rundoc/code_command/rundoc/depend_on.rb +0 -13
- data/test/fixtures/depend_on/dependency/rundoc.md +0 -5
- data/test/fixtures/depend_on/main/rundoc.md +0 -10
|
@@ -1,9 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
class Rundoc::CodeCommand::Website
|
|
2
|
-
class
|
|
4
|
+
class ScreenshotArgs
|
|
5
|
+
attr_reader :name, :upload
|
|
6
|
+
|
|
3
7
|
def initialize(name:, upload: false)
|
|
4
8
|
@name = name
|
|
5
9
|
@upload = upload
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class ScreenshotRunner
|
|
14
|
+
attr_reader :io
|
|
15
|
+
|
|
16
|
+
def initialize(user_args:, render_command:, render_result:, io:, contents: nil)
|
|
17
|
+
@name = user_args.name
|
|
18
|
+
@upload = user_args.upload
|
|
6
19
|
@driver = nil
|
|
20
|
+
@io = io
|
|
7
21
|
end
|
|
8
22
|
|
|
9
23
|
def driver
|
|
@@ -15,7 +29,7 @@ class Rundoc::CodeCommand::Website
|
|
|
15
29
|
end
|
|
16
30
|
|
|
17
31
|
def call(env = {})
|
|
18
|
-
puts "Taking screenshot: #{driver.current_url}"
|
|
32
|
+
io.puts "Taking screenshot: #{driver.current_url}"
|
|
19
33
|
filename = driver.screenshot(
|
|
20
34
|
upload: @upload,
|
|
21
35
|
screenshots_dir: env[:context].screenshots_dir
|
|
@@ -25,14 +39,6 @@ class Rundoc::CodeCommand::Website
|
|
|
25
39
|
env[:before] << ""
|
|
26
40
|
""
|
|
27
41
|
end
|
|
28
|
-
|
|
29
|
-
# def hidden?
|
|
30
|
-
# true
|
|
31
|
-
# end
|
|
32
|
-
|
|
33
|
-
# def not_hidden?
|
|
34
|
-
# !hidden?
|
|
35
|
-
# end
|
|
36
42
|
end
|
|
37
43
|
end
|
|
38
|
-
Rundoc.register_code_command(:"website.screenshot", Rundoc::CodeCommand::Website::
|
|
44
|
+
Rundoc.register_code_command(keyword: :"website.screenshot", args_klass: Rundoc::CodeCommand::Website::ScreenshotArgs, runner_klass: Rundoc::CodeCommand::Website::ScreenshotRunner)
|
|
@@ -1,14 +1,33 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class Rundoc::CodeCommand::Website
|
|
4
|
-
class
|
|
5
|
-
|
|
4
|
+
class VisitArgs
|
|
5
|
+
attr_reader :name, :url, :scroll, :height, :width, :visible, :max_attempts
|
|
6
|
+
|
|
7
|
+
def initialize(name:, url: nil, scroll: nil, height: 720, width: 1024, visible: false, max_attempts: 3)
|
|
6
8
|
@name = name
|
|
7
9
|
@url = url
|
|
8
10
|
@scroll = scroll
|
|
9
11
|
@height = height
|
|
10
12
|
@width = width
|
|
11
13
|
@visible = visible
|
|
14
|
+
@max_attempts = max_attempts
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class VisitRunner
|
|
19
|
+
attr_reader :io, :contents
|
|
20
|
+
|
|
21
|
+
def initialize(user_args:, render_command:, render_result:, io:, contents: nil)
|
|
22
|
+
@name = user_args.name
|
|
23
|
+
@url = user_args.url
|
|
24
|
+
@scroll = user_args.scroll
|
|
25
|
+
@height = user_args.height
|
|
26
|
+
@width = user_args.width
|
|
27
|
+
@visible = user_args.visible
|
|
28
|
+
@max_attempts = user_args.max_attempts
|
|
29
|
+
@io = io
|
|
30
|
+
@contents = contents.dup if contents && !contents.empty?
|
|
12
31
|
end
|
|
13
32
|
|
|
14
33
|
def driver
|
|
@@ -17,7 +36,8 @@ class Rundoc::CodeCommand::Website
|
|
|
17
36
|
url: @url,
|
|
18
37
|
height: @height,
|
|
19
38
|
width: @width,
|
|
20
|
-
visible: @visible
|
|
39
|
+
visible: @visible,
|
|
40
|
+
io: io
|
|
21
41
|
).tap do |driver|
|
|
22
42
|
Driver.add(@name, driver)
|
|
23
43
|
end
|
|
@@ -31,9 +51,9 @@ class Rundoc::CodeCommand::Website
|
|
|
31
51
|
message = "Visting: #{@url}"
|
|
32
52
|
message << "and executing:\n#{contents}" unless contents.nil? || contents.empty?
|
|
33
53
|
|
|
34
|
-
puts message
|
|
54
|
+
io.puts message
|
|
35
55
|
|
|
36
|
-
driver.visit(@url) if @url
|
|
56
|
+
driver.visit(@url, max_attempts: @max_attempts) if @url
|
|
37
57
|
driver.scroll(@scroll) if @scroll
|
|
38
58
|
|
|
39
59
|
return "" if contents.nil? || contents.empty?
|
|
@@ -41,15 +61,7 @@ class Rundoc::CodeCommand::Website
|
|
|
41
61
|
|
|
42
62
|
""
|
|
43
63
|
end
|
|
44
|
-
|
|
45
|
-
def hidden?
|
|
46
|
-
true
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def not_hidden?
|
|
50
|
-
!hidden?
|
|
51
|
-
end
|
|
52
64
|
end
|
|
53
65
|
end
|
|
54
66
|
|
|
55
|
-
Rundoc.register_code_command(:"website.visit", Rundoc::CodeCommand::Website::
|
|
67
|
+
Rundoc.register_code_command(keyword: :"website.visit", args_klass: Rundoc::CodeCommand::Website::VisitArgs, runner_klass: Rundoc::CodeCommand::Website::VisitRunner)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Rundoc
|
|
2
|
-
|
|
4
|
+
module CodeCommand
|
|
3
5
|
module FileUtil
|
|
4
6
|
def filename
|
|
5
7
|
files = Dir.glob(@filename)
|
|
@@ -15,16 +17,42 @@ module Rundoc
|
|
|
15
17
|
end
|
|
16
18
|
end
|
|
17
19
|
|
|
18
|
-
class
|
|
19
|
-
|
|
20
|
+
class WriteArgs
|
|
21
|
+
attr_reader :path
|
|
22
|
+
|
|
23
|
+
def initialize(path)
|
|
24
|
+
@path = Pathname(path)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class WriteRunner
|
|
29
|
+
NEWLINE = Object.new
|
|
30
|
+
def NEWLINE.to_s
|
|
31
|
+
""
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def NEWLINE.empty?
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
include Rundoc::CodeCommand::FileUtil
|
|
39
|
+
|
|
40
|
+
attr_reader :io, :contents
|
|
41
|
+
|
|
42
|
+
def initialize(user_args:, render_command:, render_result:, io:, contents: nil)
|
|
43
|
+
@filename = user_args.path.to_s
|
|
44
|
+
@io = io
|
|
45
|
+
@render_command = render_command
|
|
46
|
+
@contents = contents.dup if contents && !contents.empty?
|
|
47
|
+
end
|
|
20
48
|
|
|
21
|
-
def
|
|
22
|
-
@
|
|
49
|
+
def render_command?
|
|
50
|
+
@render_command
|
|
23
51
|
end
|
|
24
52
|
|
|
25
53
|
def to_md(env)
|
|
26
54
|
if render_command?
|
|
27
|
-
if env[:commands].any? { |c| c[:
|
|
55
|
+
if env[:commands].any? { |c| c[:visibility].not_hidden? }
|
|
28
56
|
raise "must call write in its own code section"
|
|
29
57
|
end
|
|
30
58
|
env[:before] << "In file `#{filename}` write:"
|
|
@@ -34,7 +62,7 @@ module Rundoc
|
|
|
34
62
|
end
|
|
35
63
|
|
|
36
64
|
def call(env = {})
|
|
37
|
-
puts "Writing to: '#{filename}'"
|
|
65
|
+
io.puts "Writing to: '#{filename}'"
|
|
38
66
|
mkdir_p
|
|
39
67
|
File.write(filename, contents)
|
|
40
68
|
contents
|
|
@@ -43,8 +71,8 @@ module Rundoc
|
|
|
43
71
|
end
|
|
44
72
|
end
|
|
45
73
|
|
|
46
|
-
Rundoc.register_code_command(:write, Rundoc::CodeCommand::
|
|
47
|
-
Rundoc.register_code_command(:"file.write", Rundoc::CodeCommand::
|
|
74
|
+
Rundoc.register_code_command(keyword: :write, args_klass: Rundoc::CodeCommand::WriteArgs, runner_klass: Rundoc::CodeCommand::WriteRunner)
|
|
75
|
+
Rundoc.register_code_command(keyword: :"file.write", args_klass: Rundoc::CodeCommand::WriteArgs, runner_klass: Rundoc::CodeCommand::WriteRunner)
|
|
48
76
|
|
|
49
77
|
require "rundoc/code_command/file_command/append"
|
|
50
78
|
require "rundoc/code_command/file_command/remove"
|
data/lib/rundoc/code_command.rb
CHANGED
|
@@ -1,55 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
# Generic CodeCommand class to be inherited
|
|
3
|
-
#
|
|
4
|
-
class CodeCommand
|
|
5
|
-
# Newlines are stripped and re-added, this tells the project that
|
|
6
|
-
# we're intentionally wanting an extra newline
|
|
7
|
-
NEWLINE = Object.new
|
|
8
|
-
def NEWLINE.to_s
|
|
9
|
-
""
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def NEWLINE.empty?
|
|
13
|
-
false
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
attr_accessor :render_result, :render_command,
|
|
17
|
-
:command, :contents, :keyword,
|
|
18
|
-
:original_args
|
|
19
|
-
|
|
20
|
-
alias_method :render_result?, :render_result
|
|
21
|
-
alias_method :render_command?, :render_command
|
|
22
|
-
|
|
23
|
-
def initialize(*args)
|
|
24
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
25
2
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def not_hidden?
|
|
31
|
-
!hidden?
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def push(contents)
|
|
35
|
-
@contents ||= ""
|
|
36
|
-
@contents << contents
|
|
37
|
-
end
|
|
38
|
-
alias_method :<<, :push
|
|
39
|
-
|
|
40
|
-
# Executes command to build project
|
|
41
|
-
# Is expected to return the result of the command
|
|
42
|
-
def call(env = {})
|
|
43
|
-
raise "not implemented on #{inspect}"
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# the output of the command, i.e. `$ cat foo.txt`
|
|
47
|
-
def to_md(env = {})
|
|
48
|
-
raise "not implemented on #{inspect}"
|
|
49
|
-
end
|
|
3
|
+
module Rundoc
|
|
4
|
+
module CodeCommand
|
|
50
5
|
end
|
|
51
6
|
end
|
|
52
7
|
|
|
8
|
+
require "rundoc/code_command/deferred"
|
|
53
9
|
require "rundoc/code_command/bash"
|
|
54
10
|
require "rundoc/code_command/pipe"
|
|
55
11
|
require "rundoc/code_command/write"
|
|
@@ -61,3 +17,4 @@ require "rundoc/code_command/website"
|
|
|
61
17
|
require "rundoc/code_command/print/text"
|
|
62
18
|
require "rundoc/code_command/print/erb"
|
|
63
19
|
require "rundoc/code_command/pre/erb"
|
|
20
|
+
require "rundoc/code_command/comment"
|
data/lib/rundoc/document.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Rundoc
|
|
2
4
|
# Represents a single rundoc file on disk,
|
|
3
5
|
#
|
|
@@ -11,7 +13,8 @@ module Rundoc
|
|
|
11
13
|
|
|
12
14
|
attr_reader :contents, :stack, :context
|
|
13
15
|
|
|
14
|
-
def initialize(contents, context:)
|
|
16
|
+
def initialize(contents, context:, io: $stdout)
|
|
17
|
+
@io = io
|
|
15
18
|
@context = context
|
|
16
19
|
@contents = contents
|
|
17
20
|
@original = contents.dup
|
|
@@ -59,7 +62,8 @@ module Rundoc
|
|
|
59
62
|
fence: match[:fence],
|
|
60
63
|
lang: match[:lang],
|
|
61
64
|
code: match[:contents],
|
|
62
|
-
context: context
|
|
65
|
+
context: context,
|
|
66
|
+
io: @io
|
|
63
67
|
)
|
|
64
68
|
end
|
|
65
69
|
@contents = tail
|
|
@@ -15,7 +15,7 @@ module Rundoc
|
|
|
15
15
|
def executed_commands
|
|
16
16
|
raise "Nothing executed" unless @env[:commands].any?
|
|
17
17
|
|
|
18
|
-
@env[:commands].map { |c| c[:
|
|
18
|
+
@env[:commands].map { |c| c[:visibility] }
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
# @param fence [String] the fence used to start the code block like "```".
|
|
@@ -24,7 +24,8 @@ module Rundoc
|
|
|
24
24
|
# @param code [String] the code block contents inside the fence.
|
|
25
25
|
# @param context [Context::Execution] The details about where
|
|
26
26
|
# the code block came from.
|
|
27
|
-
def initialize(fence:, lang:, code:, context:)
|
|
27
|
+
def initialize(fence:, lang:, code:, context:, io: $stdout)
|
|
28
|
+
@io = io
|
|
28
29
|
@fence = fence
|
|
29
30
|
@lang = lang
|
|
30
31
|
@code = code
|
|
@@ -54,12 +55,13 @@ module Rundoc
|
|
|
54
55
|
env[:after] = []
|
|
55
56
|
env[:context] = @context
|
|
56
57
|
env[:stack] = @stack
|
|
58
|
+
while (item = @stack.pop)
|
|
59
|
+
code_command = item.build(io: @io)
|
|
57
60
|
|
|
58
|
-
while (code_command = @stack.pop)
|
|
59
61
|
code_output = code_command.call(env) || ""
|
|
60
62
|
code_line = code_command.to_md(env) || ""
|
|
61
|
-
result << code_line if
|
|
62
|
-
result << code_output if
|
|
63
|
+
result << code_line if item.render_command?
|
|
64
|
+
result << code_output if item.render_result?
|
|
63
65
|
|
|
64
66
|
PARTIAL_RESULT.replace(result)
|
|
65
67
|
PARTIAL_ENV.replace(env)
|
|
@@ -67,11 +69,12 @@ module Rundoc
|
|
|
67
69
|
env[:commands] << {
|
|
68
70
|
object: code_command,
|
|
69
71
|
output: code_output,
|
|
70
|
-
command: code_line
|
|
72
|
+
command: code_line,
|
|
73
|
+
visibility: item
|
|
71
74
|
}
|
|
72
75
|
end
|
|
73
76
|
|
|
74
|
-
if env[:commands].any? { |c| c[:
|
|
77
|
+
if env[:commands].any? { |c| c[:visibility].not_hidden? }
|
|
75
78
|
@rendered = self.class.to_doc(result: result, env: env)
|
|
76
79
|
end
|
|
77
80
|
self
|
data/lib/rundoc/peg_parser.rb
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "parslet"
|
|
2
4
|
|
|
3
5
|
module Rundoc
|
|
4
6
|
class PegParser < Parslet::Parser
|
|
5
7
|
rule(:spaces) { match('\s').repeat(1) }
|
|
6
8
|
rule(:spaces?) { spaces.maybe }
|
|
9
|
+
rule(:horizontal_spaces) { match('[ \t]').repeat(1) }
|
|
7
10
|
rule(:comma) { spaces? >> str(",") >> spaces? }
|
|
8
11
|
rule(:digit) { match("[0-9]") }
|
|
9
12
|
rule(:lparen) { str("(") >> spaces? }
|
|
@@ -87,7 +90,7 @@ module Rundoc
|
|
|
87
90
|
}
|
|
88
91
|
|
|
89
92
|
rule(:seattle_method) {
|
|
90
|
-
funcall >>
|
|
93
|
+
funcall >> horizontal_spaces >>
|
|
91
94
|
args.as(:args)
|
|
92
95
|
}
|
|
93
96
|
|
|
@@ -242,8 +245,8 @@ module Rundoc
|
|
|
242
245
|
raise TransformError.new(message: message, line_and_column: line_and_column)
|
|
243
246
|
end
|
|
244
247
|
Visability.new(
|
|
245
|
-
command: command.to_s == ">"
|
|
246
|
-
result: result.to_s == ">"
|
|
248
|
+
command: command.to_s == ">",
|
|
249
|
+
result: result.to_s == ">"
|
|
247
250
|
)
|
|
248
251
|
}
|
|
249
252
|
|
|
@@ -265,16 +268,21 @@ module Rundoc
|
|
|
265
268
|
code_command
|
|
266
269
|
}
|
|
267
270
|
|
|
268
|
-
# The lines before a CodeCommand are rendered
|
|
269
|
-
# without running any code
|
|
270
271
|
rule(raw_code: simple(:raw_code)) {
|
|
271
|
-
CodeCommand::
|
|
272
|
+
deferred = CodeCommand::Deferred.new(args_instance: nil, runner_klass: CodeCommand::Raw)
|
|
273
|
+
deferred.render_command = false
|
|
274
|
+
deferred.render_result = true
|
|
275
|
+
deferred.push(raw_code.to_s)
|
|
276
|
+
deferred
|
|
272
277
|
}
|
|
273
278
|
|
|
274
|
-
# Sometimes
|
|
275
279
|
rule(raw_code: sequence(:raw_code)) {
|
|
276
|
-
|
|
277
|
-
CodeCommand::
|
|
280
|
+
visible = !raw_code.nil? && !raw_code.empty?
|
|
281
|
+
deferred = CodeCommand::Deferred.new(args_instance: nil, runner_klass: CodeCommand::Raw)
|
|
282
|
+
deferred.render_command = false
|
|
283
|
+
deferred.render_result = visible
|
|
284
|
+
deferred.push(raw_code.map(&:to_s).join)
|
|
285
|
+
deferred
|
|
278
286
|
}
|
|
279
287
|
end
|
|
280
288
|
end
|
data/lib/rundoc/version.rb
CHANGED
data/lib/rundoc.rb
CHANGED
|
@@ -5,43 +5,78 @@ require "rundoc/version"
|
|
|
5
5
|
module Rundoc
|
|
6
6
|
extend self
|
|
7
7
|
|
|
8
|
+
class UnknownCommand < StandardError; end
|
|
9
|
+
|
|
8
10
|
def code_command_from_keyword(keyword, args)
|
|
9
|
-
|
|
10
|
-
original_args = args
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
args_klass = code_command(keyword.to_sym)
|
|
12
|
+
original_args = args&.dup
|
|
13
|
+
|
|
14
|
+
if args_klass
|
|
15
|
+
runner_klass = user_args_runner[keyword]
|
|
16
|
+
|
|
17
|
+
if args.is_a?(Array) && args.last.is_a?(Hash)
|
|
18
|
+
kwargs = args.pop
|
|
19
|
+
user_args = args_klass.new(*args, **kwargs)
|
|
20
|
+
elsif args.is_a?(Hash)
|
|
21
|
+
user_args = args_klass.new(**args)
|
|
22
|
+
else
|
|
23
|
+
user_args = args_klass.new(*args)
|
|
24
|
+
end
|
|
25
|
+
elsif keyword.start_with?("#")
|
|
26
|
+
args_klass = Rundoc::CodeCommand::CommentArgs
|
|
27
|
+
runner_klass = Rundoc::CodeCommand::CommentRunner
|
|
28
|
+
remainder = keyword.to_s.delete_prefix("#")
|
|
29
|
+
comment_text = [remainder, args].compact.join(" ").strip
|
|
30
|
+
user_args = args_klass.new(comment_text.empty? ? nil : comment_text)
|
|
16
31
|
else
|
|
17
|
-
|
|
32
|
+
runner_klass = Rundoc::CodeCommand::NoSuchCommand
|
|
33
|
+
user_args = nil
|
|
18
34
|
end
|
|
19
35
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
36
|
+
deferred = CodeCommand::Deferred.new(
|
|
37
|
+
args_instance: user_args,
|
|
38
|
+
runner_klass: runner_klass,
|
|
39
|
+
always_hidden: always_hidden_commands[keyword] || keyword.start_with?("#")
|
|
40
|
+
)
|
|
41
|
+
deferred.original_args = original_args
|
|
42
|
+
deferred.keyword = keyword
|
|
43
|
+
deferred
|
|
23
44
|
rescue ArgumentError => e
|
|
24
45
|
raise ArgumentError, "Wrong method signature for #{keyword} with arguments: #{original_args.inspect}, error:\n #{e.message}"
|
|
25
46
|
end
|
|
26
47
|
|
|
48
|
+
def user_code_runner_klass
|
|
49
|
+
@user_code_runner_klass ||= {}
|
|
50
|
+
end
|
|
51
|
+
|
|
27
52
|
def parser_options
|
|
28
53
|
@parser_options ||= {}
|
|
29
54
|
end
|
|
30
55
|
|
|
31
|
-
def
|
|
32
|
-
@
|
|
56
|
+
def user_args_runner
|
|
57
|
+
@user_args_runner ||= {}
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def user_args
|
|
61
|
+
@user_args ||= {}
|
|
33
62
|
end
|
|
34
63
|
|
|
35
64
|
def code_command(keyword)
|
|
36
|
-
|
|
65
|
+
user_args[:"#{keyword}"]
|
|
37
66
|
end
|
|
38
67
|
|
|
39
68
|
def known_commands
|
|
40
|
-
|
|
69
|
+
user_args.keys
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def register_code_command(keyword:, args_klass:, runner_klass:, always_hidden: false)
|
|
73
|
+
user_args[keyword] = args_klass
|
|
74
|
+
user_args_runner[keyword] = runner_klass
|
|
75
|
+
always_hidden_commands[keyword] = always_hidden
|
|
41
76
|
end
|
|
42
77
|
|
|
43
|
-
def
|
|
44
|
-
|
|
78
|
+
def always_hidden_commands
|
|
79
|
+
@always_hidden_commands ||= {}
|
|
45
80
|
end
|
|
46
81
|
|
|
47
82
|
def configure(&block)
|
data/rundoc.gemspec
CHANGED
|
@@ -16,6 +16,8 @@ Gem::Specification.new do |gem|
|
|
|
16
16
|
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
|
17
17
|
gem.require_paths = ["lib"]
|
|
18
18
|
|
|
19
|
+
gem.required_ruby_version = ">= 3.2"
|
|
20
|
+
|
|
19
21
|
gem.add_dependency "thor"
|
|
20
22
|
gem.add_dependency "parslet", "~> 2"
|
|
21
23
|
gem.add_dependency "capybara", "~> 3"
|
|
@@ -6,21 +6,7 @@ class BackgroundStdinTest < Minitest::Test
|
|
|
6
6
|
Dir.chdir(dir) do
|
|
7
7
|
dir = Pathname(dir)
|
|
8
8
|
script = dir.join("script.rb")
|
|
9
|
-
script.write
|
|
10
|
-
$stdout.sync = true
|
|
11
|
-
|
|
12
|
-
print "> "
|
|
13
|
-
while line = gets
|
|
14
|
-
puts line
|
|
15
|
-
if line.strip == "exit"
|
|
16
|
-
puts "Bye"
|
|
17
|
-
return
|
|
18
|
-
else
|
|
19
|
-
puts "You said: #{line}"
|
|
20
|
-
end
|
|
21
|
-
print "> "
|
|
22
|
-
end
|
|
23
|
-
EOF
|
|
9
|
+
script.write loop_script
|
|
24
10
|
|
|
25
11
|
source_path = dir.join("RUNDOC.md")
|
|
26
12
|
source_path.write <<~EOF
|
|
@@ -54,4 +40,68 @@ class BackgroundStdinTest < Minitest::Test
|
|
|
54
40
|
end
|
|
55
41
|
end
|
|
56
42
|
end
|
|
43
|
+
|
|
44
|
+
def test_print_output_on_exit
|
|
45
|
+
Dir.mktmpdir do |dir|
|
|
46
|
+
Dir.chdir(dir) do
|
|
47
|
+
dir = Pathname(dir)
|
|
48
|
+
script = dir.join("script.rb")
|
|
49
|
+
script.write loop_script
|
|
50
|
+
|
|
51
|
+
source_path = dir.join("RUNDOC.md")
|
|
52
|
+
source_path.write <<~EOF
|
|
53
|
+
```
|
|
54
|
+
:::-- background.start("ruby #{script}",
|
|
55
|
+
name: "background_ungraceful_exit",
|
|
56
|
+
wait: ">",
|
|
57
|
+
timeout: 15
|
|
58
|
+
)
|
|
59
|
+
:::-- background.stdin_write("hello", name: "background_ungraceful_exit", wait: "hello")
|
|
60
|
+
```
|
|
61
|
+
EOF
|
|
62
|
+
|
|
63
|
+
io = StringIO.new
|
|
64
|
+
Rundoc::CLI.new(
|
|
65
|
+
io: io,
|
|
66
|
+
source_path: source_path,
|
|
67
|
+
on_success_dir: dir.join(SUCCESS_DIRNAME)
|
|
68
|
+
).call
|
|
69
|
+
|
|
70
|
+
logs = io.string
|
|
71
|
+
|
|
72
|
+
match_after = partition_match_after(actual: logs, include_str: "Warning background task is still running, cleaning up: `background_ungraceful_exit`")
|
|
73
|
+
match_after = partition_match_after(actual: match_after, include_str: "Log contents for `/usr/bin/env bash -c")
|
|
74
|
+
partition_match_after(actual: match_after, include_str: "> hello")
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Finds the include_str if it exists or raises an error
|
|
80
|
+
# Returns the contents of that string and everything after it
|
|
81
|
+
#
|
|
82
|
+
# Used to handle the case where output might be in the logs twice and we want to verify the order
|
|
83
|
+
def partition_match_after(actual:, include_str:)
|
|
84
|
+
_before, match, after = actual.partition(include_str)
|
|
85
|
+
found = match && !match.empty?
|
|
86
|
+
assert found, "Expected to find `#{include_str}` in output, but did not. Output:\n#{actual}"
|
|
87
|
+
[match, after].join
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def loop_script
|
|
91
|
+
<<~'EOF'
|
|
92
|
+
$stdout.sync = true
|
|
93
|
+
|
|
94
|
+
print "> "
|
|
95
|
+
while line = gets
|
|
96
|
+
puts line
|
|
97
|
+
if line.strip == "exit"
|
|
98
|
+
puts "Bye"
|
|
99
|
+
return
|
|
100
|
+
else
|
|
101
|
+
puts "You said: #{line}"
|
|
102
|
+
end
|
|
103
|
+
print "> "
|
|
104
|
+
end
|
|
105
|
+
EOF
|
|
106
|
+
end
|
|
57
107
|
end
|
|
@@ -34,4 +34,23 @@ class IntegrationWebsiteTest < Minitest::Test
|
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
|
+
|
|
38
|
+
def test_retry_client_closed_early
|
|
39
|
+
tcp_unexpected_exit do |port|
|
|
40
|
+
io = StringIO.new
|
|
41
|
+
driver = Rundoc::CodeCommand::Website::Driver.new(name: SecureRandom.hex, url: nil, read_timeout: 0.1, io: io)
|
|
42
|
+
assert_raises(Net::ReadTimeout) do
|
|
43
|
+
driver.visit("http://localhost:#{port}", max_attempts: 3, delay: 0)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
logs = io.string
|
|
47
|
+
assert_logs_include(logs: logs, include_str: "Error visiting url (1/3)")
|
|
48
|
+
assert_logs_include(logs: logs, include_str: "Error visiting url (2/3)")
|
|
49
|
+
assert_logs_include(logs: logs, include_str: "Error visiting url (3/3)")
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def assert_logs_include(logs:, include_str:)
|
|
54
|
+
assert logs.include?(include_str), "Expected logs to include #{include_str} but they didnt. Logs:\n#{logs}"
|
|
55
|
+
end
|
|
37
56
|
end
|