rundoc 0.0.2 → 1.0.0

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