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.
Files changed (63) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/check_changelog.yml +13 -0
  3. data/.gitignore +9 -0
  4. data/.travis.yml +8 -0
  5. data/CHANGELOG.md +22 -0
  6. data/Dockerfile +24 -0
  7. data/Gemfile +1 -0
  8. data/README.md +276 -74
  9. data/bin/rundoc +4 -52
  10. data/lib/rundoc.rb +15 -1
  11. data/lib/rundoc/cli.rb +84 -0
  12. data/lib/rundoc/code_command.rb +25 -6
  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 +16 -11
  23. data/lib/rundoc/code_command/file_command/remove.rb +6 -5
  24. data/lib/rundoc/code_command/no_such_command.rb +4 -0
  25. data/lib/rundoc/code_command/pipe.rb +18 -5
  26. data/lib/rundoc/code_command/raw.rb +18 -0
  27. data/lib/rundoc/code_command/rundoc/depend_on.rb +37 -0
  28. data/lib/rundoc/code_command/rundoc/require.rb +41 -0
  29. data/lib/rundoc/code_command/rundoc_command.rb +6 -2
  30. data/lib/rundoc/code_command/website.rb +7 -0
  31. data/lib/rundoc/code_command/website/driver.rb +111 -0
  32. data/lib/rundoc/code_command/website/navigate.rb +29 -0
  33. data/lib/rundoc/code_command/website/screenshot.rb +28 -0
  34. data/lib/rundoc/code_command/website/visit.rb +47 -0
  35. data/lib/rundoc/code_command/write.rb +22 -7
  36. data/lib/rundoc/code_section.rb +41 -66
  37. data/lib/rundoc/parser.rb +5 -4
  38. data/lib/rundoc/peg_parser.rb +282 -0
  39. data/lib/rundoc/version.rb +2 -2
  40. data/rundoc.gemspec +9 -3
  41. data/test/fixtures/build_logs/rundoc.md +56 -0
  42. data/test/fixtures/depend_on/dependency/rundoc.md +5 -0
  43. data/test/fixtures/depend_on/main/rundoc.md +10 -0
  44. data/test/fixtures/java/rundoc.md +9 -0
  45. data/test/fixtures/rails_4/rundoc.md +151 -188
  46. data/test/fixtures/rails_5/rundoc.md +445 -0
  47. data/test/fixtures/rails_6/rundoc.md +451 -0
  48. data/test/fixtures/require/dependency/rundoc.md +5 -0
  49. data/test/fixtures/require/main/rundoc.md +10 -0
  50. data/test/fixtures/screenshot/rundoc.md +10 -0
  51. data/test/rundoc/code_commands/append_file_test.rb +33 -6
  52. data/test/rundoc/code_commands/background_test.rb +69 -0
  53. data/test/rundoc/code_commands/bash_test.rb +1 -1
  54. data/test/rundoc/code_commands/pipe_test.rb +1 -1
  55. data/test/rundoc/code_commands/remove_contents_test.rb +3 -4
  56. data/test/rundoc/code_section_test.rb +95 -2
  57. data/test/rundoc/parser_test.rb +7 -13
  58. data/test/rundoc/peg_parser_test.rb +381 -0
  59. data/test/rundoc/regex_test.rb +6 -6
  60. data/test/rundoc/test_parse_java.rb +1 -1
  61. data/test/test_helper.rb +1 -3
  62. metadata +143 -18
  63. 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 = 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
@@ -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
- @line_number = Integer(line) if line
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 `#{@filename}`, on line #{@line_number} add:\n\n#{before}"
20
+ env[:before] = "In file `#{filename}`, on line #{@line_number} add:\n\n#{before}"
14
21
  else
15
- env[:before] = "At the end of `#{@filename}` add:\n\n#{before}"
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 #{@filename} to have at least #{@line_number} but only has #{lines.count}" if lines.count < @line_number
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
- dir = File.expand_path("../", @filename)
54
- FileUtils.mkdir_p(dir)
55
-
56
- doc = File.read(@filename)
60
+ mkdir_p
61
+ doc = File.read(filename)
57
62
  if @line_number
58
- puts "Writing to: '#{@filename}' line #{@line_number} with: #{contents.inspect}"
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: '#{@filename}' with: #{contents.inspect}"
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(@filename, "w") do |f|
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 `#{@filename}` remove:\n\n#{before}"
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 #{@filename}"
17
- raise "#{@filename} does not exist" unless File.exist?(@filename)
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(@filename)
21
+ doc = File.read(filename)
21
22
  doc.sub!(regex, '')
22
23
 
23
- File.open(@filename, "w") do |f|
24
+ File.open(filename, "w") do |f|
24
25
  f.write(doc)
25
26
  end
26
27
  contents
@@ -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,12 +6,8 @@ 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
-
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(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
 
22
+ Rundoc.register_code_command(:rundoc, RundocCommand)
23
+ Rundoc.register_code_command(:"rundoc.configure", RundocCommand)
21
24
 
22
- Rundoc.register_code_command(:rundoc, ::Rundoc::CodeCommand::RundocCommand)
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)