rundoc 0.0.2 → 1.0.0

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 (37) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +8 -0
  3. data/CHANGELOG.md +3 -0
  4. data/Gemfile +1 -0
  5. data/README.md +63 -15
  6. data/bin/rundoc +8 -0
  7. data/lib/rundoc.rb +14 -1
  8. data/lib/rundoc/code_command.rb +18 -4
  9. data/lib/rundoc/code_command/background.rb +9 -0
  10. data/lib/rundoc/code_command/background/log/clear.rb +17 -0
  11. data/lib/rundoc/code_command/background/log/read.rb +16 -0
  12. data/lib/rundoc/code_command/background/process_spawn.rb +70 -0
  13. data/lib/rundoc/code_command/background/start.rb +36 -0
  14. data/lib/rundoc/code_command/background/stop.rb +17 -0
  15. data/lib/rundoc/code_command/background/wait.rb +19 -0
  16. data/lib/rundoc/code_command/bash.rb +1 -1
  17. data/lib/rundoc/code_command/bash/cd.rb +1 -1
  18. data/lib/rundoc/code_command/file_command/append.rb +2 -0
  19. data/lib/rundoc/code_command/raw.rb +18 -0
  20. data/lib/rundoc/code_command/rundoc_command.rb +2 -1
  21. data/lib/rundoc/code_section.rb +21 -74
  22. data/lib/rundoc/peg_parser.rb +282 -0
  23. data/lib/rundoc/version.rb +1 -1
  24. data/rundoc.gemspec +2 -1
  25. data/test/fixtures/rails_5/rundoc.md +70 -74
  26. data/test/rundoc/code_commands/append_file_test.rb +2 -2
  27. data/test/rundoc/code_commands/background_test.rb +43 -0
  28. data/test/rundoc/code_commands/bash_test.rb +1 -1
  29. data/test/rundoc/code_commands/pipe_test.rb +1 -1
  30. data/test/rundoc/code_commands/remove_contents_test.rb +1 -1
  31. data/test/rundoc/code_section_test.rb +6 -5
  32. data/test/rundoc/parser_test.rb +2 -7
  33. data/test/rundoc/peg_parser_test.rb +348 -0
  34. data/test/rundoc/regex_test.rb +1 -1
  35. data/test/rundoc/test_parse_java.rb +1 -1
  36. data/test/test_helper.rb +1 -1
  37. metadata +33 -4
@@ -0,0 +1,17 @@
1
+ class Rundoc::CodeCommand::Background
2
+ class Stop < Rundoc::CodeCommand
3
+ def initialize(name: )
4
+ @spawn = Rundoc::CodeCommand::Background::ProcessSpawn.find(name)
5
+ end
6
+
7
+ def to_md(env = {})
8
+ ""
9
+ end
10
+
11
+ def call(env = {})
12
+ @spawn.stop
13
+ ""
14
+ end
15
+ end
16
+ end
17
+ Rundoc.register_code_command(:"background.stop", Rundoc::CodeCommand::Background::Stop)
@@ -0,0 +1,19 @@
1
+ class Rundoc::CodeCommand::Background
2
+ class Wait < Rundoc::CodeCommand
3
+ def initialize(name: , wait:, timeout: 5)
4
+ @spawn = Rundoc::CodeCommand::Background::ProcessSpawn.find(name)
5
+ @wait = wait
6
+ @timeout_value = Integer(timeout)
7
+ end
8
+
9
+ def to_md(env = {})
10
+ ""
11
+ end
12
+
13
+ def call(env = {})
14
+ @spawn.wait(@wait, @timeout_value)
15
+ ""
16
+ end
17
+ end
18
+ end
19
+ Rundoc.register_code_command(:"background.wait", Rundoc::CodeCommand::Background::Wait)
@@ -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,7 +5,7 @@ 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
9
  end
10
10
 
11
11
  def call(env)
@@ -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
@@ -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
@@ -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 = {})
@@ -82,83 +82,30 @@ module Rundoc
82
82
  commands.map(&:not_hidden?).detect {|c| c }
83
83
  end
84
84
 
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
85
  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
86
+ parser = Rundoc::PegParser.new.code_block
87
+ tree = parser.parse(@code)
88
+ actual = Rundoc::PegTransformer.new.apply(tree)
89
+ actual = [actual] unless actual.is_a?(Array)
90
+ actual.each do |code_command|
91
+ @stack << "\n" if commands.last.is_a?(Rundoc::CodeCommand)
92
+ @stack << code_command
93
+ commands << code_command
109
94
  end
95
+ rescue ::Parslet::ParseFailed => e
96
+ raise "Could not compile code:\n#{@code}\nReason: #{e.message}"
110
97
  end
111
98
 
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}"
144
- end
145
-
146
- @stack << "\n" if commands.last.is_a?(Rundoc::CodeCommand)
147
- @stack << code_command
148
- commands << code_command
149
- code_command
150
- end
151
-
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
99
+ # def check_parse_error(command, code_block)
100
+ # return unless code_command = @stack.last
101
+ # return unless code_command.is_a?(Rundoc::CodeCommand::NoSuchCommand)
102
+ # @original.lines.each_with_index do |line, index|
103
+ # next unless line == command
104
+ # raise ParseError.new(keyword: code_command.keyword,
105
+ # block: code_block,
106
+ # command: command,
107
+ # line_number: index.next)
108
+ # end
109
+ # end
163
110
  end
164
111
  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 #>> 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