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.
Files changed (60) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/check_changelog.yml +13 -0
  3. data/.gitignore +7 -0
  4. data/.travis.yml +8 -0
  5. data/CHANGELOG.md +26 -0
  6. data/Dockerfile +24 -0
  7. data/Gemfile +1 -0
  8. data/README.md +261 -92
  9. data/bin/rundoc +4 -60
  10. data/lib/rundoc.rb +15 -1
  11. data/lib/rundoc/cli.rb +84 -0
  12. data/lib/rundoc/code_command.rb +20 -5
  13. data/lib/rundoc/code_command/background.rb +9 -0
  14. data/lib/rundoc/code_command/background/log/clear.rb +17 -0
  15. data/lib/rundoc/code_command/background/log/read.rb +16 -0
  16. data/lib/rundoc/code_command/background/process_spawn.rb +96 -0
  17. data/lib/rundoc/code_command/background/start.rb +38 -0
  18. data/lib/rundoc/code_command/background/stop.rb +17 -0
  19. data/lib/rundoc/code_command/background/wait.rb +19 -0
  20. data/lib/rundoc/code_command/bash.rb +1 -1
  21. data/lib/rundoc/code_command/bash/cd.rb +21 -3
  22. data/lib/rundoc/code_command/file_command/append.rb +2 -0
  23. data/lib/rundoc/code_command/no_such_command.rb +4 -0
  24. data/lib/rundoc/code_command/pipe.rb +17 -4
  25. data/lib/rundoc/code_command/raw.rb +18 -0
  26. data/lib/rundoc/code_command/rundoc/depend_on.rb +37 -0
  27. data/lib/rundoc/code_command/rundoc/require.rb +41 -0
  28. data/lib/rundoc/code_command/rundoc_command.rb +6 -2
  29. data/lib/rundoc/code_command/website.rb +7 -0
  30. data/lib/rundoc/code_command/website/driver.rb +111 -0
  31. data/lib/rundoc/code_command/website/navigate.rb +29 -0
  32. data/lib/rundoc/code_command/website/screenshot.rb +28 -0
  33. data/lib/rundoc/code_command/website/visit.rb +47 -0
  34. data/lib/rundoc/code_section.rb +34 -78
  35. data/lib/rundoc/parser.rb +4 -3
  36. data/lib/rundoc/peg_parser.rb +282 -0
  37. data/lib/rundoc/version.rb +1 -1
  38. data/rundoc.gemspec +9 -3
  39. data/test/fixtures/build_logs/rundoc.md +56 -0
  40. data/test/fixtures/depend_on/dependency/rundoc.md +5 -0
  41. data/test/fixtures/depend_on/main/rundoc.md +10 -0
  42. data/test/fixtures/java/rundoc.md +9 -0
  43. data/test/fixtures/rails_4/rundoc.md +1 -1
  44. data/test/fixtures/rails_5/rundoc.md +76 -74
  45. data/test/fixtures/{rails_5_beta → rails_6}/rundoc.md +93 -87
  46. data/test/fixtures/require/dependency/rundoc.md +5 -0
  47. data/test/fixtures/require/main/rundoc.md +10 -0
  48. data/test/fixtures/screenshot/rundoc.md +10 -0
  49. data/test/rundoc/code_commands/append_file_test.rb +2 -2
  50. data/test/rundoc/code_commands/background_test.rb +69 -0
  51. data/test/rundoc/code_commands/bash_test.rb +1 -1
  52. data/test/rundoc/code_commands/pipe_test.rb +12 -2
  53. data/test/rundoc/code_commands/remove_contents_test.rb +1 -1
  54. data/test/rundoc/code_section_test.rb +6 -5
  55. data/test/rundoc/parser_test.rb +2 -8
  56. data/test/rundoc/peg_parser_test.rb +391 -0
  57. data/test/rundoc/regex_test.rb +1 -1
  58. data/test/rundoc/test_parse_java.rb +1 -1
  59. data/test/test_helper.rb +1 -1
  60. 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 = 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
- Dir.chdir(line)
15
- nil
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
@@ -12,6 +12,8 @@ class Rundoc::CodeCommand::FileCommand
12
12
  end
13
13
 
14
14
  def to_md(env)
15
+ return unless render_command?
16
+
15
17
  raise "must call write in its own code section" unless env[:commands].empty?
16
18
  before = env[:before]
17
19
  if @line_number
@@ -1,6 +1,10 @@
1
1
  module Rundoc
2
2
  class CodeCommand
3
3
  class NoSuchCommand < Rundoc::CodeCommand
4
+
5
+ def call(env = {})
6
+ raise "No such command registered with rundoc: #{@keyword.inspect} for '#{@keyword} #{@original_args}'"
7
+ end
4
8
  end
5
9
  end
6
10
  end
@@ -6,10 +6,7 @@ module Rundoc
6
6
  # ::: | tail -n 2
7
7
  # => "test\ntmp.file\n"
8
8
  def initialize(line)
9
- line_array = line.split(" ")
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(line)
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,7 @@
1
+ class Rundoc::CodeCommand::Website
2
+ end
3
+
4
+ require 'rundoc/code_command/website/driver'
5
+ require 'rundoc/code_command/website/screenshot'
6
+ require 'rundoc/code_command/website/visit'
7
+ require 'rundoc/code_command/website/navigate'
@@ -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] = "![Screenshot of #{@driver.current_url}](#{filename})"
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)