rundoc 0.0.1 → 1.1.1
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 +9 -0
- data/.travis.yml +8 -0
- data/CHANGELOG.md +22 -0
- data/Dockerfile +24 -0
- data/Gemfile +1 -0
- data/README.md +276 -74
- data/bin/rundoc +4 -52
- data/lib/rundoc.rb +15 -1
- data/lib/rundoc/cli.rb +84 -0
- data/lib/rundoc/code_command.rb +25 -6
- 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 +16 -11
- data/lib/rundoc/code_command/file_command/remove.rb +6 -5
- data/lib/rundoc/code_command/no_such_command.rb +4 -0
- data/lib/rundoc/code_command/pipe.rb +18 -5
- 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_command/write.rb +22 -7
- data/lib/rundoc/code_section.rb +41 -66
- data/lib/rundoc/parser.rb +5 -4
- data/lib/rundoc/peg_parser.rb +282 -0
- data/lib/rundoc/version.rb +2 -2
- 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 +151 -188
- data/test/fixtures/rails_5/rundoc.md +445 -0
- data/test/fixtures/rails_6/rundoc.md +451 -0
- 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 +33 -6
- 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 +1 -1
- data/test/rundoc/code_commands/remove_contents_test.rb +3 -4
- data/test/rundoc/code_section_test.rb +95 -2
- data/test/rundoc/parser_test.rb +7 -13
- data/test/rundoc/peg_parser_test.rb +381 -0
- data/test/rundoc/regex_test.rb +6 -6
- data/test/rundoc/test_parse_java.rb +1 -1
- data/test/test_helper.rb +1 -3
- metadata +143 -18
- data/Gemfile.lock +0 -38
@@ -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
|
@@ -1,18 +1,25 @@
|
|
1
1
|
class Rundoc::CodeCommand::FileCommand
|
2
2
|
class Append < Rundoc::CodeCommand
|
3
|
+
include FileUtil
|
3
4
|
|
4
5
|
def initialize(filename)
|
5
6
|
@filename, line = filename.split('#')
|
6
|
-
|
7
|
+
if line
|
8
|
+
@line_number = Integer(line)
|
9
|
+
else
|
10
|
+
@line_number = nil
|
11
|
+
end
|
7
12
|
end
|
8
13
|
|
9
14
|
def to_md(env)
|
15
|
+
return unless render_command?
|
16
|
+
|
10
17
|
raise "must call write in its own code section" unless env[:commands].empty?
|
11
18
|
before = env[:before]
|
12
19
|
if @line_number
|
13
|
-
env[:before] = "In file `#{
|
20
|
+
env[:before] = "In file `#{filename}`, on line #{@line_number} add:\n\n#{before}"
|
14
21
|
else
|
15
|
-
env[:before] = "At the end of `#{
|
22
|
+
env[:before] = "At the end of `#{filename}` add:\n\n#{before}"
|
16
23
|
end
|
17
24
|
nil
|
18
25
|
end
|
@@ -36,7 +43,7 @@ class Rundoc::CodeCommand::FileCommand
|
|
36
43
|
|
37
44
|
def insert_contents_into_at_line(doc)
|
38
45
|
lines = doc.lines
|
39
|
-
raise "Expected #{
|
46
|
+
raise "Expected #{filename} to have at least #{@line_number} but only has #{lines.count}" if lines.count < @line_number
|
40
47
|
result = []
|
41
48
|
lines.each_with_index do |line, index|
|
42
49
|
line_number = index.next
|
@@ -50,19 +57,17 @@ class Rundoc::CodeCommand::FileCommand
|
|
50
57
|
end
|
51
58
|
|
52
59
|
def call(env = {})
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
doc = File.read(@filename)
|
60
|
+
mkdir_p
|
61
|
+
doc = File.read(filename)
|
57
62
|
if @line_number
|
58
|
-
puts "Writing to: '#{
|
63
|
+
puts "Writing to: '#{filename}' line #{@line_number} with: #{contents.inspect}"
|
59
64
|
doc = insert_contents_into_at_line(doc)
|
60
65
|
else
|
61
|
-
puts "Appending to file: '#{
|
66
|
+
puts "Appending to file: '#{filename}' with: #{contents.inspect}"
|
62
67
|
doc = concat_with_newline(doc, contents)
|
63
68
|
end
|
64
69
|
|
65
|
-
File.open(
|
70
|
+
File.open(filename, "w") do |f|
|
66
71
|
f.write(doc)
|
67
72
|
end
|
68
73
|
contents
|
@@ -1,5 +1,6 @@
|
|
1
1
|
class Rundoc::CodeCommand::FileCommand
|
2
2
|
class Remove < Rundoc::CodeCommand
|
3
|
+
include FileUtil
|
3
4
|
|
4
5
|
def initialize(filename)
|
5
6
|
@filename = filename
|
@@ -8,19 +9,19 @@ class Rundoc::CodeCommand::FileCommand
|
|
8
9
|
def to_md(env)
|
9
10
|
raise "must call write in its own code section" unless env[:commands].empty?
|
10
11
|
before = env[:before]
|
11
|
-
env[:before] = "In file `#{
|
12
|
+
env[:before] = "In file `#{filename}` remove:\n\n#{before}"
|
12
13
|
nil
|
13
14
|
end
|
14
15
|
|
15
16
|
def call(env = {})
|
16
|
-
puts "Deleting '#{contents.strip}' from #{
|
17
|
-
raise "#{
|
17
|
+
puts "Deleting '#{contents.strip}' from #{filename}"
|
18
|
+
raise "#{filename} does not exist" unless File.exist?(filename)
|
18
19
|
|
19
20
|
regex = /^\s*#{Regexp.quote(contents)}/
|
20
|
-
doc = File.read(
|
21
|
+
doc = File.read(filename)
|
21
22
|
doc.sub!(regex, '')
|
22
23
|
|
23
|
-
File.open(
|
24
|
+
File.open(filename, "w") do |f|
|
24
25
|
f.write(doc)
|
25
26
|
end
|
26
27
|
contents
|
@@ -6,12 +6,8 @@ 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
|
-
|
15
11
|
# before: "",
|
16
12
|
# after: "",
|
17
13
|
# commands:
|
@@ -27,6 +23,23 @@ module Rundoc
|
|
27
23
|
def to_md(env = {})
|
28
24
|
""
|
29
25
|
end
|
26
|
+
|
27
|
+
private def parse(code)
|
28
|
+
parser = Rundoc::PegParser.new.command
|
29
|
+
tree = parser.parse(code)
|
30
|
+
actual = Rundoc::PegTransformer.new.apply(tree)
|
31
|
+
|
32
|
+
if actual.is_a?(Array)
|
33
|
+
return actual.first
|
34
|
+
else
|
35
|
+
return actual
|
36
|
+
end
|
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
|
|
22
|
+
Rundoc.register_code_command(:rundoc, RundocCommand)
|
23
|
+
Rundoc.register_code_command(:"rundoc.configure", RundocCommand)
|
21
24
|
|
22
|
-
|
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)
|