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
@@ -32,6 +32,7 @@ module Rundoc
32
32
  @commands = []
33
33
  @stack = []
34
34
  @keyword = options[:keyword] or raise "keyword is required"
35
+ @document_path = options[:document_path]
35
36
  @fence = match[:fence]
36
37
  @lang = match[:lang]
37
38
  @code = match[:contents]
@@ -42,8 +43,9 @@ module Rundoc
42
43
  result = []
43
44
  env = {}
44
45
  env[:commands] = []
45
- env[:before] = "#{fence}#{lang}"
46
- env[:after] = "#{fence}"
46
+ env[:before] = String.new("#{fence}#{lang}")
47
+ env[:after] = String.new(fence)
48
+ env[:document_path] = @document_path
47
49
 
48
50
  @stack.each do |s|
49
51
  unless s.respond_to?(:call)
@@ -55,7 +57,7 @@ module Rundoc
55
57
  code_output = code_command.call(env) || ""
56
58
  code_line = code_command.to_md(env) || ""
57
59
 
58
- env[:commands] << { object: code_command, output: code_output, command: code_line}
60
+ env[:commands] << { object: code_command, output: code_output, command: code_line }
59
61
 
60
62
  tmp_result = []
61
63
  tmp_result << code_line if code_command.render_command?
@@ -65,10 +67,17 @@ module Rundoc
65
67
  result
66
68
  end
67
69
 
70
+ return env[:replace] if env[:replace]
71
+
68
72
  return "" if hidden?
69
73
 
70
74
  array = [env[:before], result, env[:after]]
71
- return array.flatten.compact.map(&:rstrip).reject(&:empty?).join("\n") << "\n"
75
+ array.flatten!
76
+ array.compact!
77
+ array.map!(&:rstrip)
78
+ array.reject!(&:empty?)
79
+
80
+ return array.join("\n") << "\n"
72
81
  end
73
82
 
74
83
  # all of the commands are hidden
@@ -82,83 +91,30 @@ module Rundoc
82
91
  commands.map(&:not_hidden?).detect {|c| c }
83
92
  end
84
93
 
85
- def command_regex
86
- COMMAND_REGEX.call(keyword)
87
- end
88
-
89
- def add_code(match, line)
90
- add_match_to_code_command(match, commands)
91
- check_parse_error(line, code)
92
- end
93
-
94
- def add_contents(line)
95
- if commands.empty?
96
- @stack << line
97
- else
98
- commands.last << line
99
- end
100
- end
101
-
102
94
  def parse_code_command
103
- code.lines.each do |line|
104
- if match = line.match(command_regex)
105
- add_code(match, line)
106
- else
107
- add_contents(line)
108
- end
109
- end
110
- end
111
-
112
- def add_match_to_code_command(match, commands)
113
- command = match[:command]
114
- tag = match[:tag]
115
- statement = match[:statement]
116
- code_command = Rundoc.code_command_from_keyword(command, statement)
117
-
118
- case tag
119
- when /\A>>/ # show command, show result
120
- code_command.render_command = true
121
- code_command.render_result = true
122
- when /(\A>\-)|(\A>[^\-]?)/ # show command, not result
123
- code_command.render_command = true
124
- code_command.render_result = false
125
- when /\A\->/ # hide command, show result
126
- code_command.render_command = false
127
- code_command.render_result = true
128
- when /(\A\-)|(\A\-\-)/ # hide command, hide result
129
- code_command.render_command = false
130
- code_command.render_result = false
131
- # ========= DEPRECATED ==========
132
- when /\=/
133
- puts "Deprecated: `:::=` is deprecated use `:::>>` instead"
134
- puts " :::>> #{command} #{statement}"
135
- code_command.render_command = true
136
- code_command.render_result = true
137
- when /\s/
138
- puts "Deprecated: `:::` is deprecated use `:::>` instead"
139
- puts " :::> #{command} #{statement}"
140
- code_command.render_command = true
141
- code_command.render_result = false
142
- else
143
- raise "Tag is invalid #{tag.inspect}"
95
+ parser = Rundoc::PegParser.new.code_block
96
+ tree = parser.parse(@code)
97
+ actual = Rundoc::PegTransformer.new.apply(tree)
98
+ actual = [actual] unless actual.is_a?(Array)
99
+ actual.each do |code_command|
100
+ @stack << "\n" if commands.last.is_a?(Rundoc::CodeCommand)
101
+ @stack << code_command
102
+ commands << code_command
144
103
  end
145
-
146
- @stack << "\n" if commands.last.is_a?(Rundoc::CodeCommand)
147
- @stack << code_command
148
- commands << code_command
149
- code_command
104
+ rescue ::Parslet::ParseFailed => e
105
+ raise "Could not compile code:\n#{@code}\nReason: #{e.message}"
150
106
  end
151
107
 
152
- def check_parse_error(command, code_block)
153
- return unless code_command = @stack.last
154
- return unless code_command.is_a?(Rundoc::CodeCommand::NoSuchCommand)
155
- @original.lines.each_with_index do |line, index|
156
- next unless line == command
157
- raise ParseError.new(keyword: code_command.keyword,
158
- block: code_block,
159
- command: command,
160
- line_number: index.next)
161
- end
162
- end
108
+ # def check_parse_error(command, code_block)
109
+ # return unless code_command = @stack.last
110
+ # return unless code_command.is_a?(Rundoc::CodeCommand::NoSuchCommand)
111
+ # @original.lines.each_with_index do |line, index|
112
+ # next unless line == command
113
+ # raise ParseError.new(keyword: code_command.keyword,
114
+ # block: code_block,
115
+ # command: command,
116
+ # line_number: index.next)
117
+ # end
118
+ # end
163
119
  end
164
120
  end
@@ -10,10 +10,11 @@ module Rundoc
10
10
 
11
11
  attr_reader :contents, :keyword, :stack
12
12
 
13
- def initialize(contents, options = {})
13
+ def initialize(contents, keyword: DEFAULT_KEYWORD, document_path: nil)
14
+ @document_path = document_path
14
15
  @contents = contents
15
16
  @original = contents.dup
16
- @keyword = options[:keyword] || DEFAULT_KEYWORD
17
+ @keyword = keyword
17
18
  @stack = []
18
19
  partition
19
20
  end
@@ -42,7 +43,7 @@ module Rundoc
42
43
  @stack << head unless head.empty?
43
44
  unless code.empty?
44
45
  match = code.match(CODEBLOCK_REGEX)
45
- @stack << CodeSection.new(match, keyword: keyword)
46
+ @stack << CodeSection.new(match, keyword: keyword, document_path: @document_path)
46
47
  end
47
48
  @contents = tail
48
49
  end
@@ -0,0 +1,282 @@
1
+ require 'parslet'
2
+
3
+ module Rundoc
4
+ class PegParser < Parslet::Parser
5
+ rule(:spaces) { match('\s').repeat(1) }
6
+ rule(:spaces?) { spaces.maybe }
7
+ rule(:comma) { spaces? >> str(',') >> spaces? }
8
+ rule(:digit) { match('[0-9]') }
9
+ rule(:lparen) { str('(') >> spaces? }
10
+ rule(:rparen) { str(')') }
11
+ rule(:newline) { str("\r").maybe >> str("\n") }
12
+
13
+ rule(:singlequote_string) {
14
+ str("'") >> (
15
+ str("'").absnt? >> any
16
+ ).repeat.as(:string) >>
17
+ str("'") >> spaces?
18
+ }
19
+ rule(:doublequote_string) {
20
+ str('"') >> (
21
+ str('"').absnt? >> any
22
+ ).repeat.as(:string) >>
23
+ str('"') >> spaces?
24
+ }
25
+ rule(:string) { doublequote_string | singlequote_string }
26
+
27
+ rule(:number) {
28
+ (
29
+ str('-').maybe >> (
30
+ str('0') | (match('[1-9]') >> digit.repeat)
31
+ ) >> (
32
+ str('.') >> digit.repeat(1)
33
+ ).maybe >> (
34
+ match('[eE]') >> (str('+') | str('-')).maybe >> digit.repeat(1)
35
+ ).maybe
36
+ ).as(:number)
37
+ }
38
+
39
+ rule(:value) {
40
+ string |
41
+ number |
42
+ str('true').as(:true) |
43
+ str('false').as(:false) |
44
+ str('nil').as(:nil)
45
+ }
46
+
47
+ rule(:key) {
48
+ spaces? >> (
49
+ str(':').absent? >> match('\s').absent? >> any
50
+ ).repeat.as(:key) >> str(':') >> spaces?
51
+ }
52
+
53
+ rule(:key_value) {
54
+ (
55
+ key >> value.as(:val)
56
+ ).as(:key_value) >> spaces?
57
+ }
58
+
59
+ rule(:named_args) {
60
+ spaces? >> (
61
+ key_value >> (comma >> key_value).repeat
62
+ ).as(:named_args) >>
63
+ spaces?
64
+ }
65
+
66
+ rule(:unquoted_string) {
67
+ (newline.absent? >> any).repeat.as(:string) #>> newline
68
+ }
69
+
70
+ rule(:positional_args) {
71
+ spaces? >> value.as(:val) >> (comma >> value.as(:val)).repeat >>
72
+ comma.maybe >> named_args.maybe
73
+ }
74
+
75
+ rule(:args) {
76
+ (positional_args | named_args | string | unquoted_string)
77
+ }
78
+
79
+ rule(:funcall) {
80
+ spaces? >> (newline.absent? >> match('[^ \(\)]')).repeat(1).as(:funcall)
81
+ }
82
+
83
+ rule(:parens_method) {
84
+ funcall >> lparen >>
85
+ args.as(:args) >>
86
+ rparen
87
+ }
88
+
89
+ rule(:seattle_method) {
90
+ funcall >> spaces >>
91
+ (args).as(:args)
92
+ }
93
+
94
+ rule(:no_args_method) {
95
+ spaces? >> ( lparen.absent? >> rparen.absent? >> spaces.absent? >> any).repeat(1)
96
+ }
97
+
98
+ rule(:method_call) {
99
+ (parens_method | seattle_method | no_args_method).as(:method_call)
100
+ }
101
+
102
+ # >>
103
+ # >-
104
+ # ->
105
+ # --
106
+ rule(:visability) {
107
+ (
108
+ match('>|-').maybe.as(:vis_command) >> match('>|-').maybe.as(:vis_result)
109
+ ).as(:visability)
110
+ }
111
+
112
+ # :::
113
+ rule(:start_command) {
114
+ match(/\A:/) >> str('::')
115
+ }
116
+
117
+ # :::>> $ cat foo.rb
118
+ rule(:command) {
119
+ (
120
+ start_command >>
121
+ visability.as(:cmd_visability) >> spaces? >>
122
+ method_call.as(:cmd_method_call) >> newline.maybe #>> match(/\z/)
123
+ ).as(:command)
124
+ }
125
+
126
+ # :::>> file.write hello.txt
127
+ # world
128
+ rule(:command_with_stdin) {
129
+ command >>
130
+ (
131
+ start_command.absent? >>
132
+ code_fence.absent? >>
133
+ any
134
+ ).repeat(1).as(:stdin) |
135
+ command
136
+ }
137
+
138
+
139
+ # :::>> file.write hello.txt
140
+ # world
141
+ # :::>> file.write foo.txt
142
+ # bar
143
+ rule(:multiple_commands) {
144
+ (command_with_stdin | command).repeat
145
+ }
146
+
147
+ rule(:code_fence) {
148
+ match(/\A`/) >> str("``")
149
+ }
150
+
151
+ rule(:fenced_commands) {
152
+ code_fence >>
153
+ match('\S').repeat >>
154
+ newline >>
155
+ multiple_commands >>
156
+ code_fence >> newline
157
+ }
158
+
159
+ rule(:raw_code) {
160
+ (start_command.absent? >> command.absent? >> any).repeat(1).as(:raw_code) >>
161
+ multiple_commands.maybe
162
+ }
163
+
164
+ rule(:code_block) {
165
+ raw_code | multiple_commands
166
+ }
167
+ end
168
+ end
169
+
170
+
171
+ module Rundoc
172
+ class PegTransformer < Parslet::Transform
173
+ rule(nill: simple(:nu)) { nil }
174
+ rule(true: simple(:tr)) { true }
175
+ rule(false: simple(:fa)) { false }
176
+ rule(string: simple(:st)) { st.to_s }
177
+
178
+ rule(:number => simple(:nb)) {
179
+ nb.match(/[eE\.]/) ? Float(nb) : Integer(nb)
180
+ }
181
+
182
+ def self.convert_named_args(na)
183
+ (na.is_a?(Array) ? na : [ na ]).each_with_object({}) { |element, hash|
184
+ key = element[:key_value][:key].to_sym
185
+ val = element[:key_value][:val]
186
+ hash[key] = val
187
+ }
188
+ end
189
+
190
+ rule(:named_args => subtree(:na)) {
191
+ PegTransformer.convert_named_args(na)
192
+ }
193
+
194
+ rule(val: simple(:val)) {
195
+ val
196
+ }
197
+
198
+ # Handle the case where there is only one value
199
+ rule(val: simple(:val), named_args: subtree(:na)) {
200
+ [val, PegTransformer.convert_named_args(na)]
201
+ }
202
+
203
+ rule(method_call: subtree(:mc)) {
204
+ if mc.is_a?(::Parslet::Slice)
205
+ keyword = mc.to_sym
206
+ args = nil
207
+ else
208
+ keyword = mc[:funcall].to_sym
209
+ args = mc[:args]
210
+ end
211
+
212
+ Rundoc.code_command_from_keyword(keyword, args)
213
+ }
214
+
215
+ class Visability
216
+ attr_reader :command, :result
217
+ alias :command? :command
218
+ alias :result? :result
219
+ def initialize(command:, result:)
220
+ @command = command
221
+ @result = result
222
+ end
223
+ end
224
+
225
+ class TransformError < ::StandardError
226
+ attr_reader :line_and_column
227
+ def initialize(message: , line_and_column:)
228
+ @line_and_column = line_and_column || [1, 1]
229
+ super message
230
+ end
231
+ end
232
+
233
+ rule(:visability => {
234
+ vis_command: simple(:command),
235
+ vis_result: simple(:result)
236
+ }) {
237
+ if result.nil? || command.nil?
238
+ line_and_column = command&.line_and_column
239
+ line_and_column ||= result&.line_and_column
240
+
241
+ message = +""
242
+ message << "You attempted to use a command that does not begin with two visibility indicators. Please replace: "
243
+ message << "`:::#{command}#{result}` with `:::#{command || '-'}#{result || '-'}`"
244
+ raise TransformError.new(message: message, line_and_column: line_and_column)
245
+ end
246
+ Visability.new(
247
+ command: command.to_s == '>'.freeze,
248
+ result: result.to_s == '>'.freeze
249
+ )
250
+ }
251
+
252
+ rule(
253
+ cmd_visability: simple(:cmd_vis), # Visibility#new
254
+ cmd_method_call: simple(:code_command) # Rundoc::CodeCommand#new
255
+ ) {
256
+ code_command.render_command = cmd_vis.command?
257
+ code_command.render_result = cmd_vis.result?
258
+ code_command
259
+ }
260
+
261
+ rule(command: simple(:code_command)) {
262
+ code_command
263
+ }
264
+
265
+ rule(command: simple(:code_command), stdin: simple(:str)) {
266
+ code_command << str
267
+ code_command
268
+ }
269
+
270
+ # The lines before a CodeCommand are rendered
271
+ # without running any code
272
+ rule(raw_code: simple(:raw_code)) {
273
+ CodeCommand::Raw.new(raw_code)
274
+ }
275
+
276
+ # Sometimes
277
+ rule(raw_code: sequence(:raw_code)) {
278
+ hidden = raw_code.nil? || raw_code.empty?
279
+ CodeCommand::Raw.new(raw_code, visible: !hidden)
280
+ }
281
+ end
282
+ end