markdown_exec 1.6 → 1.8
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 +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
|
|