rundoc 0.0.2 → 1.1.2
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 +5 -5
- data/.github/workflows/check_changelog.yml +13 -0
- data/.gitignore +7 -0
- data/.travis.yml +8 -0
- data/CHANGELOG.md +26 -0
- data/Dockerfile +24 -0
- data/Gemfile +1 -0
- data/README.md +261 -92
- data/bin/rundoc +4 -60
- data/lib/rundoc.rb +15 -1
- data/lib/rundoc/cli.rb +84 -0
- data/lib/rundoc/code_command.rb +20 -5
- data/lib/rundoc/code_command/background.rb +9 -0
- data/lib/rundoc/code_command/background/log/clear.rb +17 -0
- data/lib/rundoc/code_command/background/log/read.rb +16 -0
- data/lib/rundoc/code_command/background/process_spawn.rb +96 -0
- data/lib/rundoc/code_command/background/start.rb +38 -0
- data/lib/rundoc/code_command/background/stop.rb +17 -0
- data/lib/rundoc/code_command/background/wait.rb +19 -0
- data/lib/rundoc/code_command/bash.rb +1 -1
- data/lib/rundoc/code_command/bash/cd.rb +21 -3
- data/lib/rundoc/code_command/file_command/append.rb +2 -0
- data/lib/rundoc/code_command/no_such_command.rb +4 -0
- data/lib/rundoc/code_command/pipe.rb +17 -4
- data/lib/rundoc/code_command/raw.rb +18 -0
- data/lib/rundoc/code_command/rundoc/depend_on.rb +37 -0
- data/lib/rundoc/code_command/rundoc/require.rb +41 -0
- data/lib/rundoc/code_command/rundoc_command.rb +6 -2
- data/lib/rundoc/code_command/website.rb +7 -0
- data/lib/rundoc/code_command/website/driver.rb +111 -0
- data/lib/rundoc/code_command/website/navigate.rb +29 -0
- data/lib/rundoc/code_command/website/screenshot.rb +28 -0
- data/lib/rundoc/code_command/website/visit.rb +47 -0
- data/lib/rundoc/code_section.rb +34 -78
- data/lib/rundoc/parser.rb +4 -3
- data/lib/rundoc/peg_parser.rb +282 -0
- data/lib/rundoc/version.rb +1 -1
- data/rundoc.gemspec +9 -3
- data/test/fixtures/build_logs/rundoc.md +56 -0
- data/test/fixtures/depend_on/dependency/rundoc.md +5 -0
- data/test/fixtures/depend_on/main/rundoc.md +10 -0
- data/test/fixtures/java/rundoc.md +9 -0
- data/test/fixtures/rails_4/rundoc.md +1 -1
- data/test/fixtures/rails_5/rundoc.md +76 -74
- data/test/fixtures/{rails_5_beta → rails_6}/rundoc.md +93 -87
- data/test/fixtures/require/dependency/rundoc.md +5 -0
- data/test/fixtures/require/main/rundoc.md +10 -0
- data/test/fixtures/screenshot/rundoc.md +10 -0
- data/test/rundoc/code_commands/append_file_test.rb +2 -2
- data/test/rundoc/code_commands/background_test.rb +69 -0
- data/test/rundoc/code_commands/bash_test.rb +1 -1
- data/test/rundoc/code_commands/pipe_test.rb +12 -2
- data/test/rundoc/code_commands/remove_contents_test.rb +1 -1
- data/test/rundoc/code_section_test.rb +6 -5
- data/test/rundoc/parser_test.rb +2 -8
- data/test/rundoc/peg_parser_test.rb +391 -0
- data/test/rundoc/regex_test.rb +1 -1
- data/test/rundoc/test_parse_java.rb +1 -1
- data/test/test_helper.rb +1 -1
- metadata +120 -12
@@ -43,7 +43,7 @@ class Rundoc::CodeCommand::Bash < Rundoc::CodeCommand
|
|
43
43
|
result = sanitize_escape_chars io.read
|
44
44
|
end
|
45
45
|
unless $?.success?
|
46
|
-
raise "Command `#{@line}` exited with non zero status: #{result}" unless keyword.include?("fail")
|
46
|
+
raise "Command `#{@line}` exited with non zero status: #{result}" unless keyword.to_s.include?("fail")
|
47
47
|
end
|
48
48
|
return result
|
49
49
|
end
|
@@ -5,14 +5,32 @@ class Rundoc::CodeCommand::Bash
|
|
5
5
|
class Cd < Rundoc::CodeCommand::Bash
|
6
6
|
|
7
7
|
def initialize(line)
|
8
|
-
@line
|
8
|
+
@line = line
|
9
|
+
end
|
10
|
+
|
11
|
+
# Ignore duplicate chdir warnings "warning: conflicting chdir during another chdir block"
|
12
|
+
def supress_chdir_warning
|
13
|
+
old_stderr = $stderr
|
14
|
+
capture_stderr = StringIO.new
|
15
|
+
$stderr = capture_stderr
|
16
|
+
return yield
|
17
|
+
ensure
|
18
|
+
if old_stderr
|
19
|
+
$stderr = old_stderr
|
20
|
+
capture_string = capture_stderr.string
|
21
|
+
$stderr.puts capture_string if capture_string.each_line.count > 1 || !capture_string.include?("conflicting chdir")
|
22
|
+
end
|
9
23
|
end
|
10
24
|
|
11
25
|
def call(env)
|
12
26
|
line = @line.sub('cd', '').strip
|
13
27
|
puts "running $ cd #{line}"
|
14
|
-
|
15
|
-
|
28
|
+
|
29
|
+
supress_chdir_warning do
|
30
|
+
Dir.chdir(line)
|
31
|
+
end
|
32
|
+
|
33
|
+
return nil
|
16
34
|
end
|
17
35
|
end
|
18
36
|
end
|
@@ -6,10 +6,7 @@ module Rundoc
|
|
6
6
|
# ::: | tail -n 2
|
7
7
|
# => "test\ntmp.file\n"
|
8
8
|
def initialize(line)
|
9
|
-
|
10
|
-
@first = line_array.shift.strip
|
11
|
-
@delegate = Rundoc.code_command_from_keyword(@first, line_array.join(" "))
|
12
|
-
@delegate = Rundoc::CodeCommand::Bash.new(line) if @delegate.kind_of?(Rundoc::CodeCommand::NoSuchCommand)
|
9
|
+
@delegate = parse(line)
|
13
10
|
end
|
14
11
|
|
15
12
|
# before: "",
|
@@ -27,6 +24,22 @@ module Rundoc
|
|
27
24
|
def to_md(env = {})
|
28
25
|
""
|
29
26
|
end
|
27
|
+
|
28
|
+
private def parse(code)
|
29
|
+
parser = Rundoc::PegParser.new.method_call
|
30
|
+
tree = parser.parse(code)
|
31
|
+
actual = Rundoc::PegTransformer.new.apply(tree)
|
32
|
+
|
33
|
+
actual = actual.first if actual.is_a?(Array)
|
34
|
+
|
35
|
+
actual = Rundoc::CodeCommand::Bash.new(code) if actual.is_a?(Rundoc::CodeCommand::NoSuchCommand)
|
36
|
+
return actual
|
37
|
+
|
38
|
+
# Since `| tail -n 2` does not start with a `$` assume any "naked" commands
|
39
|
+
# are bash
|
40
|
+
rescue Parslet::ParseFailed
|
41
|
+
Rundoc::CodeCommand::Bash.new(code)
|
42
|
+
end
|
30
43
|
end
|
31
44
|
end
|
32
45
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Rundoc
|
2
|
+
class CodeCommand
|
3
|
+
class Raw < CodeCommand
|
4
|
+
def initialize(contents, visible: true)
|
5
|
+
@contents = contents
|
6
|
+
@render_result = visible
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env = {})
|
10
|
+
contents.to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_md(env = {})
|
14
|
+
contents.to_s
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class ::Rundoc::CodeCommand
|
2
|
+
class RundocCommand
|
3
|
+
class DependOn < ::Rundoc::CodeCommand
|
4
|
+
|
5
|
+
# Pass in the relative path of another rundoc document in order to
|
6
|
+
# run all of it's commands (but not to )
|
7
|
+
def initialize(path)
|
8
|
+
raise "Path must be relative (i.e. start with `.` or `..`. #{path.inspect} does not" unless path.start_with?(".")
|
9
|
+
@path = Pathname.new(path)
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_md(env = {})
|
13
|
+
""
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(env = {})
|
17
|
+
current_path = Pathname.new(env[:document_path]).dirname
|
18
|
+
document_path = @path.expand_path(current_path)
|
19
|
+
# Run the commands, but do not
|
20
|
+
puts "rundoc.depend_on: Start executing #{@path.to_s.inspect}"
|
21
|
+
output = Rundoc::Parser.new(document_path.read, document_path: document_path.to_s).to_md
|
22
|
+
puts "rundoc.depend_on: Done executing #{@path.to_s.inspect}, discarding intermediate document"
|
23
|
+
output
|
24
|
+
end
|
25
|
+
|
26
|
+
def hidden?
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
def not_hidden?
|
31
|
+
!hidden?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
Rundoc.register_code_command(:"rundoc.depend_on", ::Rundoc::CodeCommand::RundocCommand::DependOn)
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class ::Rundoc::CodeCommand
|
2
|
+
class RundocCommand
|
3
|
+
class Require < ::Rundoc::CodeCommand
|
4
|
+
|
5
|
+
# Pass in the relative path of another rundoc document in order to
|
6
|
+
# run all of it's commands. Resulting contents will be displayed
|
7
|
+
# in current document
|
8
|
+
def initialize(path)
|
9
|
+
raise "Path must be relative (i.e. start with `.` or `..`. #{path.inspect} does not" unless path.start_with?(".")
|
10
|
+
@path = Pathname.new(path)
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_md(env = {})
|
14
|
+
""
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env = {})
|
18
|
+
env[:replace] ||= String.new
|
19
|
+
current_path = Pathname.new(env[:document_path]).dirname
|
20
|
+
document_path = @path.expand_path(current_path)
|
21
|
+
|
22
|
+
puts "rundoc.require: Start executing #{@path.to_s.inspect}"
|
23
|
+
output = Rundoc::Parser.new(document_path.read, document_path: document_path.to_s).to_md
|
24
|
+
puts "rundoc.require: Done executing #{@path.to_s.inspect}, putting contents into document"
|
25
|
+
|
26
|
+
env[:replace] << output
|
27
|
+
return ""
|
28
|
+
end
|
29
|
+
|
30
|
+
def hidden?
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def not_hidden?
|
35
|
+
!hidden?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
Rundoc.register_code_command(:"rundoc.require", ::Rundoc::CodeCommand::RundocCommand::Require)
|
@@ -2,7 +2,8 @@ module ::Rundoc
|
|
2
2
|
class CodeCommand
|
3
3
|
class ::RundocCommand < ::Rundoc::CodeCommand
|
4
4
|
|
5
|
-
def initialize(
|
5
|
+
def initialize(contents = "")
|
6
|
+
@contents = contents
|
6
7
|
end
|
7
8
|
|
8
9
|
def to_md(env = {})
|
@@ -18,5 +19,8 @@ module ::Rundoc
|
|
18
19
|
end
|
19
20
|
end
|
20
21
|
|
21
|
-
|
22
22
|
Rundoc.register_code_command(:rundoc, RundocCommand)
|
23
|
+
Rundoc.register_code_command(:"rundoc.configure", RundocCommand)
|
24
|
+
|
25
|
+
require 'rundoc/code_command/rundoc/depend_on'
|
26
|
+
require 'rundoc/code_command/rundoc/require'
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'capybara'
|
2
|
+
|
3
|
+
Capybara::Selenium::Driver.load_selenium
|
4
|
+
|
5
|
+
class Rundoc::CodeCommand::Website
|
6
|
+
class Driver
|
7
|
+
attr_reader :session
|
8
|
+
|
9
|
+
def initialize(name: , url: , width: 1024, height: 720, visible: false)
|
10
|
+
browser_options = ::Selenium::WebDriver::Chrome::Options.new
|
11
|
+
browser_options.args << '--headless' unless visible
|
12
|
+
browser_options.args << '--disable-gpu' if Gem.win_platform?
|
13
|
+
browser_options.args << '--hide-scrollbars'
|
14
|
+
# browser_options.args << "--window-size=#{width},#{height}"
|
15
|
+
@width = width
|
16
|
+
@height = height
|
17
|
+
|
18
|
+
@driver = Capybara::Selenium::Driver.new(nil, browser: :chrome, options: browser_options)
|
19
|
+
driver_name = "rundoc_driver_#{name}".to_sym
|
20
|
+
Capybara.register_driver(driver_name) do |app|
|
21
|
+
@driver
|
22
|
+
end
|
23
|
+
|
24
|
+
@session = Capybara::Session.new(driver_name)
|
25
|
+
end
|
26
|
+
|
27
|
+
def visit(url)
|
28
|
+
@session.visit(url)
|
29
|
+
end
|
30
|
+
|
31
|
+
def timestamp
|
32
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S%L%N")
|
33
|
+
end
|
34
|
+
|
35
|
+
def current_url
|
36
|
+
session.current_url
|
37
|
+
end
|
38
|
+
|
39
|
+
def scroll(value = 100)
|
40
|
+
session.execute_script "window.scrollBy(0,#{value})"
|
41
|
+
end
|
42
|
+
|
43
|
+
def teardown
|
44
|
+
session.reset_session!
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.tasks
|
48
|
+
@tasks
|
49
|
+
end
|
50
|
+
|
51
|
+
def safe_eval(code)
|
52
|
+
@driver.send(:eval, code)
|
53
|
+
rescue => e
|
54
|
+
msg = String.new
|
55
|
+
msg << "Error running code #{code.inspect} at #{current_url.inspect}\n"
|
56
|
+
msg << "saving a screenshot to: `tmp/error.png`"
|
57
|
+
puts msg
|
58
|
+
session.save_screenshot("tmp/error.png")
|
59
|
+
raise e
|
60
|
+
end
|
61
|
+
|
62
|
+
def screenshot(upload: false)
|
63
|
+
@driver.resize_window_to(@driver.current_window_handle, @width, @height)
|
64
|
+
FileUtils.mkdir_p("tmp/rundoc_screenshots")
|
65
|
+
file_name = self.class.next_screenshot_name
|
66
|
+
file_path = "tmp/rundoc_screenshots/#{file_name}"
|
67
|
+
session.save_screenshot(file_path)
|
68
|
+
|
69
|
+
return file_path unless upload
|
70
|
+
|
71
|
+
case upload
|
72
|
+
when 's3', 'aws'
|
73
|
+
puts "Uploading screenshot to S3"
|
74
|
+
require 'aws-sdk-s3'
|
75
|
+
ENV.fetch('AWS_ACCESS_KEY_ID')
|
76
|
+
s3 = Aws::S3::Resource.new(region: ENV.fetch('AWS_REGION'))
|
77
|
+
|
78
|
+
key = "#{timestamp}/#{file_name}"
|
79
|
+
obj = s3.bucket(ENV.fetch('AWS_BUCKET_NAME')).object(key)
|
80
|
+
obj.upload_file(file_path)
|
81
|
+
|
82
|
+
obj.client.put_object_acl(
|
83
|
+
acl: 'public-read' ,
|
84
|
+
bucket: ENV.fetch('AWS_BUCKET_NAME'),
|
85
|
+
key: key
|
86
|
+
)
|
87
|
+
|
88
|
+
obj.public_url
|
89
|
+
else
|
90
|
+
raise "Upload #{upload.inspect} is not valid, use 's3' instead"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
@tasks = {}
|
95
|
+
def self.add(name, value)
|
96
|
+
raise "Task named #{name.inspect} is already started, choose a different name" if @tasks[name]
|
97
|
+
@tasks[name] = value
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.find(name)
|
101
|
+
raise "Could not find task with name #{name.inspect}, known task names: #{@tasks.keys.inspect}" unless @tasks[name]
|
102
|
+
@tasks[name]
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.next_screenshot_name
|
106
|
+
@count ||= 0
|
107
|
+
@count += 1
|
108
|
+
return "screenshot_#{@count}.png"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Rundoc::CodeCommand::Website
|
2
|
+
class Navigate < Rundoc::CodeCommand
|
3
|
+
def initialize(name: )
|
4
|
+
@name = name
|
5
|
+
@driver = Rundoc::CodeCommand::Website::Driver.find(name)
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_md(env = {})
|
9
|
+
""
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(env = {})
|
13
|
+
puts "website.navigate [#{@name}]: #{contents}"
|
14
|
+
@driver.safe_eval(contents)
|
15
|
+
""
|
16
|
+
end
|
17
|
+
|
18
|
+
def hidden?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
def not_hidden?
|
23
|
+
!hidden?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
Rundoc.register_code_command(:"website.nav", Rundoc::CodeCommand::Website::Navigate)
|
29
|
+
Rundoc.register_code_command(:"website.navigate", Rundoc::CodeCommand::Website::Navigate)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Rundoc::CodeCommand::Website
|
2
|
+
class Screenshot < Rundoc::CodeCommand
|
3
|
+
def initialize(name: , upload: false)
|
4
|
+
@driver = Rundoc::CodeCommand::Website::Driver.find(name)
|
5
|
+
@upload = upload
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_md(env = {})
|
9
|
+
""
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(env = {})
|
13
|
+
puts "Taking screenshot: #{@driver.current_url}"
|
14
|
+
filename = @driver.screenshot(upload: @upload)
|
15
|
+
env[:replace] = ""
|
16
|
+
""
|
17
|
+
end
|
18
|
+
|
19
|
+
# def hidden?
|
20
|
+
# true
|
21
|
+
# end
|
22
|
+
|
23
|
+
# def not_hidden?
|
24
|
+
# !hidden?
|
25
|
+
# end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
Rundoc.register_code_command(:"website.screenshot", Rundoc::CodeCommand::Website::Screenshot)
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Rundoc::CodeCommand::Website
|
2
|
+
class Visit < Rundoc::CodeCommand
|
3
|
+
def initialize(name: , url: nil, scroll: nil, height: 720, width: 1024, visible: false)
|
4
|
+
@name = name
|
5
|
+
@url = url
|
6
|
+
@scroll = scroll
|
7
|
+
@driver = Driver.new(
|
8
|
+
name: name,
|
9
|
+
url: url,
|
10
|
+
height: height,
|
11
|
+
width: width,
|
12
|
+
visible: visible
|
13
|
+
)
|
14
|
+
Driver.add(@name, @driver)
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_md(env = {})
|
18
|
+
""
|
19
|
+
end
|
20
|
+
|
21
|
+
def call(env = {})
|
22
|
+
message = String.new("Visting: #{@url}")
|
23
|
+
message << "and executing:\n#{contents}" unless contents.nil? || contents.empty?
|
24
|
+
|
25
|
+
puts message
|
26
|
+
|
27
|
+
@driver.visit(@url) if @url
|
28
|
+
@driver.scroll(@scroll) if @scroll
|
29
|
+
|
30
|
+
|
31
|
+
return "" if contents.nil? || contents.empty?
|
32
|
+
@driver.safe_eval(contents)
|
33
|
+
|
34
|
+
return ""
|
35
|
+
end
|
36
|
+
|
37
|
+
def hidden?
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
def not_hidden?
|
42
|
+
!hidden?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
Rundoc.register_code_command(:"website.visit", Rundoc::CodeCommand::Website::Visit)
|