lib/mdoc.rb in markdown_exec-1.7 vs lib/mdoc.rb in markdown_exec-1.8
- old
+ new
@@ -19,11 +19,11 @@
# Initializes an instance of MDoc with the given table of markdown sections.
#
# @param table [Array<Hash>] An array of hashes representing markdown sections.
#
- def initialize(table)
+ def initialize(table = [])
@table = table
end
def collect_block_code_cann(fcb)
body = fcb[:body].join("\n")
@@ -45,12 +45,12 @@
def collect_block_code_shell(fcb)
# write named variables to block at top of script
#
fcb[:body].join(' ').split.compact.map do |key|
- format(opts[:block_type_port_set_format],
- { key: key, value: ENV.fetch(key, nil) })
+ ### format(opts[:block_type_port_set_format], { key: key, value: ENV.fetch(key, nil) })
+ "key: #{key}, value: #{ENV.fetch(key, nil)}"
end
end
def collect_block_code_stdout(fcb)
stdout = fcb[:stdout]
@@ -70,53 +70,67 @@
# @return [Array<Hash>] An array of code blocks required by the specified code block.
#
def collect_recursively_required_blocks(name)
name_block = get_block_by_anyname(name)
if name_block.nil? || name_block.keys.empty?
- raise "Named code block `#{name}` not found."
+ raise "Named code block `#{name}` not found. (@#{__LINE__})"
end
- all = [name_block.oname] + recursively_required(name_block[:reqs])
+ # all_dependency_names = [name_block.oname] + recursively_required(name_block[:reqs])
+ dependencies = collect_dependencies(name_block[:oname])
+ all_dependency_names = collect_unique_names(dependencies).push(name_block[:oname]).uniq
+ unmet_dependencies = all_dependency_names.dup
- # in order of appearance in document
+ # in order of appearance in document (@table)
# insert function blocks
- @table.select { |fcb| all.include? fcb.oname }
- .map do |fcb|
+ blocks = @table.select { |fcb| all_dependency_names.include? fcb[:oname] }
+ .map do |fcb|
+ unmet_dependencies.delete(fcb[:oname]) # may not exist if block name is duplicated
if (call = fcb[:call])
[get_block_by_oname("[#{call.match(/^%\((\S+) |\)/)[1]}]")
.merge({ cann: call })]
else
[]
end + [fcb]
- end.flatten(1)
+ end.flatten(1) #.tap { rbp }
+ { all_dependency_names: all_dependency_names,
+ blocks: blocks,
+ dependencies: dependencies,
+ unmet_dependencies: unmet_dependencies }
end
# Collects recursively required code blocks and returns them as an array of strings.
#
# @param name [String] The name of the code block to start the collection from.
# @return [Array<String>] An array of strings containing the collected code blocks.
#
- def collect_recursively_required_code(name, opts: {})
- code = collect_wrapped_blocks(
- blocks = collect_recursively_required_blocks(name)
- ).map do |fcb|
+ def collect_recursively_required_code(name, label_body: true, label_format_above: nil, label_format_below: nil)
+ block_search = collect_recursively_required_blocks(name)
+ block_search.merge({ code: collect_wrapped_blocks(block_search[:blocks]).map do |fcb|
if fcb[:cann]
collect_block_code_cann(fcb)
elsif fcb[:stdout]
collect_block_code_stdout(fcb)
elsif [BlockType::LINK, BlockType::OPTS,
BlockType::VARS].include? fcb[:shell]
nil
elsif fcb[:shell] == BlockType::PORT
collect_block_code_shell(fcb)
- else
+ elsif label_body
+ [label_format_above && format(label_format_above, { name: fcb[:oname] })] +
+ fcb[:body] +
+ [label_format_below && format(label_format_below, { name: fcb[:oname] })]
+ else # raw body
fcb[:body]
end
- end.compact.flatten(1)
- { blocks: blocks, code: code }
+ end.compact.flatten(1).compact })
end
+ def collect_unique_names(hash)
+ hash.values.flatten.uniq
+ end
+
# Retrieves code blocks that are wrapped
# wraps are applied from left to right
# e.g. w1 w2 => w1-before w2-before w1 w2 w2-after w1-after
#
# @return [Array<Hash>] An array of code blocks required by the specified code blocks.
@@ -207,21 +221,22 @@
(block.oname&.present? || block[:label]&.present?)
)
end
end
+ # if tr ue
# Recursively fetches required code blocks for a given list of requirements.
#
# @param reqs [Array<String>] An array of requirements to start the recursion from.
# @return [Array<String>] An array of recursively required code block names.
#
def recursively_required(reqs)
return [] unless reqs
rem = reqs
memo = []
- while rem.count.positive?
+ while rem && rem.count.positive?
rem = rem.map do |req|
next if memo.include? req
memo += [req]
get_block_by_oname(req).fetch(:reqs, [])
@@ -230,10 +245,61 @@
.flatten(1)
end
memo
end
+ # else
+ # Recursively fetches required code blocks for a given list of requirements.
+ #
+ # @param source [String] The name of the code block to start the recursion from.
+ # @return [Hash] A list of code blocks required by each source code block.
+ #
+ def recursively_required_hash(source, memo = Hash.new([]))
+ return memo unless source
+ return memo if memo.keys.include? source
+
+ block = get_block_by_anyname(source)
+ if block.nil? || block.keys.empty?
+ raise "Named code block `#{source}` not found. (@#{__LINE__})"
+ end
+
+ memo[source] = block[:reqs]
+ return memo unless memo[source]&.count&.positive?
+
+ memo[source].each do |req|
+ next if memo.keys.include? req
+
+ recursively_required_hash(req, memo)
+ end
+ memo
+ end
+
+ # end
+
+ # Recursively collects dependencies of a given source.
+ # @param source [String] The name of the initial source block.
+ # @param memo [Hash] A memoization hash to store resolved dependencies.
+ # @return [Hash] A hash mapping sources to their respective dependencies.
+ def collect_dependencies(source, memo = {})
+ return memo unless source
+
+ if (block = get_block_by_anyname(source)).nil? || block.keys.empty?
+ if true
+ return memo
+ else
+ raise "Named code block `#{source}` not found. (@#{__LINE__})"
+ end
+ end
+
+ return memo unless block[:reqs]
+
+ memo[source] = block[:reqs]
+
+ block[:reqs].each { |req| collect_dependencies(req, memo) unless memo.key?(req) }
+ memo
+ end
+
def select_elements_with_neighbor_conditions(array,
last_selected_placeholder = nil, next_selected_placeholder = nil)
selected_elements = []
last_selected = last_selected_placeholder
@@ -281,20 +347,68 @@
if $PROGRAM_NAME == __FILE__
require 'bundler/setup'
Bundler.require(:default)
require 'minitest/autorun'
+ require 'mocha/minitest'
module MarkdownExec
+ class TestMDocCollectDependencies < Minitest::Test
+ def setup
+ @mdoc = MDoc.new
+ end
+
+ def test_collect_dependencies_with_no_source
+ assert_empty @mdoc.collect_dependencies(nil)
+ end
+
+ if false # must raise error
+ def test_collect_dependencies_with_nonexistent_source
+ assert_raises(RuntimeError) { @mdoc.collect_dependencies('nonexistent') }
+ end
+ end
+
+ def test_collect_dependencies_with_valid_source
+ @mdoc.stubs(:get_block_by_anyname).with('source1').returns({ reqs: ['source2'] })
+ @mdoc.stubs(:get_block_by_anyname).with('source2').returns({ reqs: [] })
+
+ expected = { 'source1' => ['source2'], 'source2' => [] }
+ assert_equal expected, @mdoc.collect_dependencies('source1')
+ end
+ end
+
+ class TestCollectUniqueNames < Minitest::Test
+ def setup
+ @mdoc = MDoc.new
+ end
+
+ def test_empty_hash
+ assert_empty @mdoc.collect_unique_names({})
+ end
+
+ def test_single_key
+ input = { group1: %w[Alice Bob Charlie] }
+ assert_equal %w[Alice Bob Charlie], @mdoc.collect_unique_names(input)
+ end
+
+ def test_multiple_keys
+ input = { group1: %w[Alice Bob], group2: %w[Charlie Alice] }
+ assert_equal %w[Alice Bob Charlie], @mdoc.collect_unique_names(input)
+ end
+
+ def test_no_unique_names
+ input = { group1: ['Alice'], group2: ['Alice'] }
+ assert_equal ['Alice'], @mdoc.collect_unique_names(input)
+ end
+ end
+
class TestMDoc < Minitest::Test
def setup
@table = [
- { oname: 'block1', body: ['code for block1'],
- reqs: ['block2'] },
+ { oname: 'block1', body: ['code for block1'], reqs: ['block2'] },
{ oname: 'block2', body: ['code for block2'], reqs: nil },
- { oname: 'block3', body: ['code for block3'],
- reqs: ['block1'] }
+ { oname: 'block3', body: ['code for block3'], reqs: ['block1'] }
]
@doc = MDoc.new(@table)
end
# def test_collect_recursively_required_code
@@ -310,19 +424,19 @@
result_missing = @doc.get_block_by_oname('missing_block')
assert_equal({}, result_missing)
end
### broken test
- # def test_collect_recursively_required_blocks
- # result = @doc.collect_recursively_required_blocks('block3')
- # expected_result = [@table[0], @table[1], @table[2]]
- # assert_equal expected_result, result
+ def test_collect_recursively_required_blocks
+ result = @doc.collect_recursively_required_blocks('block3')[:blocks]
+ expected_result = [@table[0], @table[1], @table[2]]
+ assert_equal expected_result, result
- # assert_raises(RuntimeError) do
- # @doc.collect_recursively_required_blocks('missing_block')
- # end
- # end
+ assert_raises(RuntimeError) do
+ @doc.collect_recursively_required_blocks('missing_block')
+ end
+ end
def test_hide_menu_block_per_options
opts = { hide_blocks_by_name: true,
block_name_hidden_match: 'block1' }
block = FCB.new(oname: 'block1')
@@ -336,14 +450,15 @@
# result = @doc.fcbs_per_options(opts)
# assert_equal [@table[1], @table[2]], result
# end
def test_recursively_required
- result = @doc.recursively_required(['block3'])
- assert_equal %w[block3 block1 block2], result
+ result = @doc.recursively_required_hash('block3')
+ assert_equal ({ 'block3' => ['block1'], 'block1' => ['block2'], 'block2' => nil }),
+ result
- result_no_reqs = @doc.recursively_required(nil)
- assert_equal [], result_no_reqs
+ result_no_reqs = @doc.recursively_required_hash(nil)
+ assert_equal ({}), result_no_reqs
end
end
class TestMDoc2 < Minitest::Test
# Mocking the @table object for testing