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.
- checksums.yaml +5 -5
- data/.travis.yml +8 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +1 -0
- data/README.md +63 -15
- data/bin/rundoc +8 -0
- data/lib/rundoc.rb +14 -1
- data/lib/rundoc/code_command.rb +18 -4
- data/lib/rundoc/code_command/background.rb +9 -0
- data/lib/rundoc/code_command/background/log/clear.rb +17 -0
- data/lib/rundoc/code_command/background/log/read.rb +16 -0
- data/lib/rundoc/code_command/background/process_spawn.rb +70 -0
- data/lib/rundoc/code_command/background/start.rb +36 -0
- data/lib/rundoc/code_command/background/stop.rb +17 -0
- data/lib/rundoc/code_command/background/wait.rb +19 -0
- data/lib/rundoc/code_command/bash.rb +1 -1
- data/lib/rundoc/code_command/bash/cd.rb +1 -1
- data/lib/rundoc/code_command/file_command/append.rb +2 -0
- data/lib/rundoc/code_command/raw.rb +18 -0
- data/lib/rundoc/code_command/rundoc_command.rb +2 -1
- data/lib/rundoc/code_section.rb +21 -74
- data/lib/rundoc/peg_parser.rb +282 -0
- data/lib/rundoc/version.rb +1 -1
- data/rundoc.gemspec +2 -1
- data/test/fixtures/rails_5/rundoc.md +70 -74
- data/test/rundoc/code_commands/append_file_test.rb +2 -2
- data/test/rundoc/code_commands/background_test.rb +43 -0
- data/test/rundoc/code_commands/bash_test.rb +1 -1
- data/test/rundoc/code_commands/pipe_test.rb +1 -1
- data/test/rundoc/code_commands/remove_contents_test.rb +1 -1
- data/test/rundoc/code_section_test.rb +6 -5
- data/test/rundoc/parser_test.rb +2 -7
- data/test/rundoc/peg_parser_test.rb +348 -0
- data/test/rundoc/regex_test.rb +1 -1
- data/test/rundoc/test_parse_java.rb +1 -1
- data/test/test_helper.rb +1 -1
- 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
|
@@ -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
|
data/lib/rundoc/code_section.rb
CHANGED
@@ -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
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
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
|