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
@@ -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