rundoc 0.0.2 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
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)