markdown_exec 1.6 → 1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +5 -2
- data/Gemfile.lock +1 -1
- data/Rakefile +2 -0
- data/bin/tab_completion.sh +19 -3
- data/examples/colors.md +48 -0
- data/examples/import0.md +41 -5
- data/examples/import1.md +9 -8
- data/examples/include.md +11 -4
- data/examples/linked1.md +8 -4
- data/examples/opts.md +7 -0
- data/lib/ansi_formatter.rb +161 -0
- data/lib/array.rb +27 -0
- data/lib/array_util.rb +21 -0
- data/lib/cached_nested_file_reader.rb +51 -31
- data/lib/constants.rb +46 -0
- data/lib/directory_searcher.rb +239 -0
- data/lib/exceptions.rb +34 -0
- data/lib/fcb.rb +41 -1
- data/lib/filter.rb +32 -17
- data/lib/fout.rb +52 -0
- data/lib/hash.rb +21 -0
- data/lib/hash_delegator.rb +2796 -0
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/markdown_exec.rb +190 -1474
- data/lib/mdoc.rb +148 -36
- data/lib/menu.src.yml +224 -37
- data/lib/menu.yml +209 -33
- data/lib/method_sorter.rb +19 -17
- data/lib/object_present.rb +1 -1
- data/lib/pty1.rb +16 -16
- data/lib/regexp.rb +2 -4
- data/lib/shared.rb +0 -5
- data/lib/string_util.rb +22 -0
- metadata +13 -3
- data/lib/environment_opt_parse.rb +0 -209
data/lib/mdoc.rb
CHANGED
@@ -21,7 +21,7 @@ module MarkdownExec
|
|
21
21
|
#
|
22
22
|
# @param table [Array<Hash>] An array of hashes representing markdown sections.
|
23
23
|
#
|
24
|
-
def initialize(table)
|
24
|
+
def initialize(table = [])
|
25
25
|
@table = table
|
26
26
|
end
|
27
27
|
|
@@ -47,8 +47,8 @@ module MarkdownExec
|
|
47
47
|
# write named variables to block at top of script
|
48
48
|
#
|
49
49
|
fcb[:body].join(' ').split.compact.map do |key|
|
50
|
-
format(opts[:block_type_port_set_format],
|
51
|
-
|
50
|
+
### format(opts[:block_type_port_set_format], { key: key, value: ENV.fetch(key, nil) })
|
51
|
+
"key: #{key}, value: #{ENV.fetch(key, nil)}"
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
@@ -72,22 +72,30 @@ module MarkdownExec
|
|
72
72
|
def collect_recursively_required_blocks(name)
|
73
73
|
name_block = get_block_by_anyname(name)
|
74
74
|
if name_block.nil? || name_block.keys.empty?
|
75
|
-
raise "Named code block `#{name}` not found."
|
75
|
+
raise "Named code block `#{name}` not found. (@#{__LINE__})"
|
76
76
|
end
|
77
77
|
|
78
|
-
|
78
|
+
# all_dependency_names = [name_block.oname] + recursively_required(name_block[:reqs])
|
79
|
+
dependencies = collect_dependencies(name_block[:oname])
|
80
|
+
all_dependency_names = collect_unique_names(dependencies).push(name_block[:oname]).uniq
|
81
|
+
unmet_dependencies = all_dependency_names.dup
|
79
82
|
|
80
|
-
# in order of appearance in document
|
83
|
+
# in order of appearance in document (@table)
|
81
84
|
# insert function blocks
|
82
|
-
@table.select { |fcb|
|
83
|
-
|
85
|
+
blocks = @table.select { |fcb| all_dependency_names.include? fcb[:oname] }
|
86
|
+
.map do |fcb|
|
87
|
+
unmet_dependencies.delete(fcb[:oname]) # may not exist if block name is duplicated
|
84
88
|
if (call = fcb[:call])
|
85
89
|
[get_block_by_oname("[#{call.match(/^%\((\S+) |\)/)[1]}]")
|
86
90
|
.merge({ cann: call })]
|
87
91
|
else
|
88
92
|
[]
|
89
93
|
end + [fcb]
|
90
|
-
end.flatten(1)
|
94
|
+
end.flatten(1) #.tap { rbp }
|
95
|
+
{ all_dependency_names: all_dependency_names,
|
96
|
+
blocks: blocks,
|
97
|
+
dependencies: dependencies,
|
98
|
+
unmet_dependencies: unmet_dependencies }
|
91
99
|
end
|
92
100
|
|
93
101
|
# Collects recursively required code blocks and returns them as an array of strings.
|
@@ -95,10 +103,9 @@ module MarkdownExec
|
|
95
103
|
# @param name [String] The name of the code block to start the collection from.
|
96
104
|
# @return [Array<String>] An array of strings containing the collected code blocks.
|
97
105
|
#
|
98
|
-
def collect_recursively_required_code(name,
|
99
|
-
|
100
|
-
|
101
|
-
).map do |fcb|
|
106
|
+
def collect_recursively_required_code(name, label_body: true, label_format_above: nil, label_format_below: nil)
|
107
|
+
block_search = collect_recursively_required_blocks(name)
|
108
|
+
block_search.merge({ code: collect_wrapped_blocks(block_search[:blocks]).map do |fcb|
|
102
109
|
if fcb[:cann]
|
103
110
|
collect_block_code_cann(fcb)
|
104
111
|
elsif fcb[:stdout]
|
@@ -108,11 +115,18 @@ module MarkdownExec
|
|
108
115
|
nil
|
109
116
|
elsif fcb[:shell] == BlockType::PORT
|
110
117
|
collect_block_code_shell(fcb)
|
111
|
-
|
118
|
+
elsif label_body
|
119
|
+
[label_format_above && format(label_format_above, { name: fcb[:oname] })] +
|
120
|
+
fcb[:body] +
|
121
|
+
[label_format_below && format(label_format_below, { name: fcb[:oname] })]
|
122
|
+
else # raw body
|
112
123
|
fcb[:body]
|
113
124
|
end
|
114
|
-
end.compact.flatten(1)
|
115
|
-
|
125
|
+
end.compact.flatten(1).compact })
|
126
|
+
end
|
127
|
+
|
128
|
+
def collect_unique_names(hash)
|
129
|
+
hash.values.flatten.uniq
|
116
130
|
end
|
117
131
|
|
118
132
|
# Retrieves code blocks that are wrapped
|
@@ -209,9 +223,7 @@ module MarkdownExec
|
|
209
223
|
end
|
210
224
|
end
|
211
225
|
|
212
|
-
#
|
213
|
-
# end
|
214
|
-
|
226
|
+
# if tr ue
|
215
227
|
# Recursively fetches required code blocks for a given list of requirements.
|
216
228
|
#
|
217
229
|
# @param reqs [Array<String>] An array of requirements to start the recursion from.
|
@@ -222,7 +234,7 @@ module MarkdownExec
|
|
222
234
|
|
223
235
|
rem = reqs
|
224
236
|
memo = []
|
225
|
-
while rem.count.positive?
|
237
|
+
while rem && rem.count.positive?
|
226
238
|
rem = rem.map do |req|
|
227
239
|
next if memo.include? req
|
228
240
|
|
@@ -235,6 +247,57 @@ module MarkdownExec
|
|
235
247
|
memo
|
236
248
|
end
|
237
249
|
|
250
|
+
# else
|
251
|
+
# Recursively fetches required code blocks for a given list of requirements.
|
252
|
+
#
|
253
|
+
# @param source [String] The name of the code block to start the recursion from.
|
254
|
+
# @return [Hash] A list of code blocks required by each source code block.
|
255
|
+
#
|
256
|
+
def recursively_required_hash(source, memo = Hash.new([]))
|
257
|
+
return memo unless source
|
258
|
+
return memo if memo.keys.include? source
|
259
|
+
|
260
|
+
block = get_block_by_anyname(source)
|
261
|
+
if block.nil? || block.keys.empty?
|
262
|
+
raise "Named code block `#{source}` not found. (@#{__LINE__})"
|
263
|
+
end
|
264
|
+
|
265
|
+
memo[source] = block[:reqs]
|
266
|
+
return memo unless memo[source]&.count&.positive?
|
267
|
+
|
268
|
+
memo[source].each do |req|
|
269
|
+
next if memo.keys.include? req
|
270
|
+
|
271
|
+
recursively_required_hash(req, memo)
|
272
|
+
end
|
273
|
+
memo
|
274
|
+
end
|
275
|
+
|
276
|
+
# end
|
277
|
+
|
278
|
+
# Recursively collects dependencies of a given source.
|
279
|
+
# @param source [String] The name of the initial source block.
|
280
|
+
# @param memo [Hash] A memoization hash to store resolved dependencies.
|
281
|
+
# @return [Hash] A hash mapping sources to their respective dependencies.
|
282
|
+
def collect_dependencies(source, memo = {})
|
283
|
+
return memo unless source
|
284
|
+
|
285
|
+
if (block = get_block_by_anyname(source)).nil? || block.keys.empty?
|
286
|
+
if true
|
287
|
+
return memo
|
288
|
+
else
|
289
|
+
raise "Named code block `#{source}` not found. (@#{__LINE__})"
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
return memo unless block[:reqs]
|
294
|
+
|
295
|
+
memo[source] = block[:reqs]
|
296
|
+
|
297
|
+
block[:reqs].each { |req| collect_dependencies(req, memo) unless memo.key?(req) }
|
298
|
+
memo
|
299
|
+
end
|
300
|
+
|
238
301
|
def select_elements_with_neighbor_conditions(array,
|
239
302
|
last_selected_placeholder = nil, next_selected_placeholder = nil)
|
240
303
|
selected_elements = []
|
@@ -286,16 +349,64 @@ if $PROGRAM_NAME == __FILE__
|
|
286
349
|
Bundler.require(:default)
|
287
350
|
|
288
351
|
require 'minitest/autorun'
|
352
|
+
require 'mocha/minitest'
|
289
353
|
|
290
354
|
module MarkdownExec
|
355
|
+
class TestMDocCollectDependencies < Minitest::Test
|
356
|
+
def setup
|
357
|
+
@mdoc = MDoc.new
|
358
|
+
end
|
359
|
+
|
360
|
+
def test_collect_dependencies_with_no_source
|
361
|
+
assert_empty @mdoc.collect_dependencies(nil)
|
362
|
+
end
|
363
|
+
|
364
|
+
if false # must raise error
|
365
|
+
def test_collect_dependencies_with_nonexistent_source
|
366
|
+
assert_raises(RuntimeError) { @mdoc.collect_dependencies('nonexistent') }
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
def test_collect_dependencies_with_valid_source
|
371
|
+
@mdoc.stubs(:get_block_by_anyname).with('source1').returns({ reqs: ['source2'] })
|
372
|
+
@mdoc.stubs(:get_block_by_anyname).with('source2').returns({ reqs: [] })
|
373
|
+
|
374
|
+
expected = { 'source1' => ['source2'], 'source2' => [] }
|
375
|
+
assert_equal expected, @mdoc.collect_dependencies('source1')
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
class TestCollectUniqueNames < Minitest::Test
|
380
|
+
def setup
|
381
|
+
@mdoc = MDoc.new
|
382
|
+
end
|
383
|
+
|
384
|
+
def test_empty_hash
|
385
|
+
assert_empty @mdoc.collect_unique_names({})
|
386
|
+
end
|
387
|
+
|
388
|
+
def test_single_key
|
389
|
+
input = { group1: %w[Alice Bob Charlie] }
|
390
|
+
assert_equal %w[Alice Bob Charlie], @mdoc.collect_unique_names(input)
|
391
|
+
end
|
392
|
+
|
393
|
+
def test_multiple_keys
|
394
|
+
input = { group1: %w[Alice Bob], group2: %w[Charlie Alice] }
|
395
|
+
assert_equal %w[Alice Bob Charlie], @mdoc.collect_unique_names(input)
|
396
|
+
end
|
397
|
+
|
398
|
+
def test_no_unique_names
|
399
|
+
input = { group1: ['Alice'], group2: ['Alice'] }
|
400
|
+
assert_equal ['Alice'], @mdoc.collect_unique_names(input)
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
291
404
|
class TestMDoc < Minitest::Test
|
292
405
|
def setup
|
293
406
|
@table = [
|
294
|
-
{ oname: 'block1', body: ['code for block1'],
|
295
|
-
reqs: ['block2'] },
|
407
|
+
{ oname: 'block1', body: ['code for block1'], reqs: ['block2'] },
|
296
408
|
{ oname: 'block2', body: ['code for block2'], reqs: nil },
|
297
|
-
{ oname: 'block3', body: ['code for block3'],
|
298
|
-
reqs: ['block1'] }
|
409
|
+
{ oname: 'block3', body: ['code for block3'], reqs: ['block1'] }
|
299
410
|
]
|
300
411
|
@doc = MDoc.new(@table)
|
301
412
|
end
|
@@ -315,15 +426,15 @@ if $PROGRAM_NAME == __FILE__
|
|
315
426
|
end
|
316
427
|
|
317
428
|
### broken test
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
429
|
+
def test_collect_recursively_required_blocks
|
430
|
+
result = @doc.collect_recursively_required_blocks('block3')[:blocks]
|
431
|
+
expected_result = [@table[0], @table[1], @table[2]]
|
432
|
+
assert_equal expected_result, result
|
322
433
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
434
|
+
assert_raises(RuntimeError) do
|
435
|
+
@doc.collect_recursively_required_blocks('missing_block')
|
436
|
+
end
|
437
|
+
end
|
327
438
|
|
328
439
|
def test_hide_menu_block_per_options
|
329
440
|
opts = { hide_blocks_by_name: true,
|
@@ -341,11 +452,12 @@ if $PROGRAM_NAME == __FILE__
|
|
341
452
|
# end
|
342
453
|
|
343
454
|
def test_recursively_required
|
344
|
-
result = @doc.
|
345
|
-
assert_equal
|
455
|
+
result = @doc.recursively_required_hash('block3')
|
456
|
+
assert_equal ({ 'block3' => ['block1'], 'block1' => ['block2'], 'block2' => nil }),
|
457
|
+
result
|
346
458
|
|
347
|
-
result_no_reqs = @doc.
|
348
|
-
assert_equal
|
459
|
+
result_no_reqs = @doc.recursively_required_hash(nil)
|
460
|
+
assert_equal ({}), result_no_reqs
|
349
461
|
end
|
350
462
|
end
|
351
463
|
|