class MarkdownExec::MDoc
to fetch related or dependent blocks.
of the document, such as code blocks. It also supports recursion
It provides methods to extract and manipulate specific sections
MDoc represents an imported markdown document.
#
def collect_block_code_cann(fcb)
def collect_block_code_cann(fcb) body = fcb[:body].join("\n") xcall = fcb[:cann][1..-2] mstdin = xcall.match(/<(?<type>\$)?(?<name>[A-Za-z_\-.\w]+)/) mstdout = xcall.match(/>(?<type>\$)?(?<name>[A-Za-z_\-.\w]+)/) yqcmd = if mstdin[:type] "echo \"$#{mstdin[:name]}\" | yq '#{body}'" else "yq e '#{body}' '#{mstdin[:name]}'" end if mstdout[:type] "export #{mstdout[:name]}=$(#{yqcmd})" else "#{yqcmd} > '#{mstdout[:name]}'" end end
def collect_block_code_shell(fcb)
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) }) "key: #{key}, value: #{ENV.fetch(key, nil)}" end end
def collect_block_code_stdout(fcb)
def collect_block_code_stdout(fcb) stdout = fcb[:stdout] body = fcb[:body].join("\n") if stdout[:type] %(export #{stdout[:name]}=$(cat <<"EOF"\n#{body}\nEOF\n)) else "cat > '#{stdout[:name]}' <<\"EOF\"\n" \ "#{body}\n" \ "EOF\n" end end
def collect_block_dependencies(anyname:)
- 
        (Array- An array of code blocks required by the specified code block.) 
Parameters:
- 
        name(String) -- The name of the code block to start the retrieval from.
def collect_block_dependencies(anyname:) name_block = get_block_by_anyname(anyname) if name_block.nil? || name_block.keys.empty? raise "Named code block `#{anyname}` not found. (@#{__LINE__})" end nickname = name_block[:nickname] || name_block[:oname] dependencies = collect_dependencies(nickname) # &bc 'dependencies.count:',dependencies.count all_dependency_names = collect_unique_names(dependencies).push(nickname).uniq # &bc 'all_dependency_names.count:',all_dependency_names.count # select non-chrome blocks in order of appearance in source documents # blocks = @table.select do |fcb| !fcb.fetch(:chrome, false) && all_dependency_names.include?(fcb.fetch(:nickname, nil) || fcb.fetch(:oname)) end # &bc 'blocks.count:',blocks.count ## add cann key to blocks, calc unmet_dependencies # unmet_dependencies = all_dependency_names.dup blocks = blocks.map do |fcb| unmet_dependencies.delete(fcb[:nickname] || fcb[:oname]) # may not exist if block name is duplicated if (call = fcb[:call]) [get_block_by_anyname("[#{call.match(/^%\((\S+) |\)/)[1]}]") .merge({ cann: call })] else [] end + [fcb] end.flatten(1) # &bc 'unmet_dependencies.count:',unmet_dependencies.count { all_dependency_names: all_dependency_names, blocks: blocks, dependencies: dependencies, unmet_dependencies: unmet_dependencies } end
def collect_dependencies(source, memo = {})
- 
        (Hash)- A hash mapping sources to their respective dependencies.
Parameters:
- 
        memo(Hash) -- A memoization hash to store resolved dependencies.
- 
        source(String) -- The name of the initial source block.
def collect_dependencies(source, memo = {}) return memo unless source if (block = get_block_by_anyname(source)).nil? || block.keys.empty? return memo if true raise "Named code block `#{source}` not found. (@#{__LINE__})" 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 collect_recursively_required_code(anyname:, block_source:, label_body: true, label_format_above: nil,
- 
        (Array- An array of strings containing the collected code blocks.) 
Parameters:
- 
        name(String) -- The name of the code block to start the collection from.
def collect_recursively_required_code(anyname:, block_source:, label_body: true, label_format_above: nil, label_format_below: nil) block_search = collect_block_dependencies(anyname: anyname) if block_search[:blocks] blocks = collect_wrapped_blocks(block_search[:blocks]) # &bc 'blocks.count:',blocks.count block_search.merge( { block_names: blocks.map { |block| block[:nickname] || block[:oname] }, code: 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) elsif label_body block_name_for_bash_comment = (fcb[:nickname] || fcb[:oname]).gsub(/\s+/, '_') [label_format_above && format(label_format_above, block_source.merge({ block_name: block_name_for_bash_comment }))] + fcb[:body] + [label_format_below && format(label_format_below, block_source.merge({ block_name: block_name_for_bash_comment }))] else # raw body fcb[:body] end end.compact.flatten(1).compact } ) else block_search.merge({ block_names: [], code: [] }) end rescue StandardError error_handler('collect_recursively_required_code') end
def collect_unique_names(hash)
def collect_unique_names(hash) hash.values.flatten.uniq end
def collect_wrapped_blocks(blocks)
- 
        (Array- An array of code blocks required by the specified code blocks.) 
def collect_wrapped_blocks(blocks) blocks.map do |block| (block[:wraps] || []).map do |wrap| wrap_before = wrap.sub('}', '-before}') ### hardcoded wrap name @table.select { |fcb| [wrap_before, wrap].include? fcb.oname } end.flatten(1) + [block] + (block[:wraps] || []).reverse.map do |wrap| wrap_after = wrap.sub('}', '-after}') ### hardcoded wrap name @table.select { |fcb| fcb.oname == wrap_after } end.flatten(1) end.flatten(1).compact end
def error_handler(name = '', opts = {})
def error_handler(name = '', opts = {}) Exceptions.error_handler( "MDoc.#{name} -- #{$!}", opts ) end
def fcbs_per_options(opts = {})
- 
        (Array- An array of code blocks that match the options.) 
Parameters:
- 
        opts(Hash) -- The options used for filtering code blocks.
def fcbs_per_options(opts = {}) options = opts.merge(block_name_hidden_match: nil) selrows = @table.select do |fcb_title_groups| Filter.fcb_select? options, fcb_title_groups end ### hide rows correctly if !options[:menu_include_imported_blocks] selrows = selrows.reject do |block| block.fetch(:depth, 0).positive? end end if opts[:hide_blocks_by_name] selrows = selrows.reject do |block| hide_menu_block_on_name opts, block end end # remove # . empty chrome between code; edges are same as blanks # select_elements_with_neighbor_conditions(selrows) do |prev_element, current, next_element| !(current[:chrome] && !current[:oname].present?) || !(!prev_element.nil? && prev_element[:shell].present? && !next_element.nil? && next_element[:shell].present?) end end
def get_block_by_anyname(name, default = {})
- 
        (Hash)- The code block as a hash or the default value if not found.
Parameters:
- 
        default(Hash) -- The default value to return if the code block is not found.
- 
        name(String) -- The name of the code block to retrieve.
def get_block_by_anyname(name, default = {}) @table.select do |fcb| fcb.fetch(:nickname, '') == name || fcb.fetch(:dname, '') == name || fcb.fetch(:oname, '') == name end.fetch(0, default) end
def hide_menu_block_on_name(opts, block)
- 
        (Boolean)- True if the code block should be hidden; false otherwise.
Parameters:
- 
        block(Hash) -- The code block to check for hiding.
- 
        opts(Hash) -- The options used for hiding code blocks.
def hide_menu_block_on_name(opts, block) if block.fetch(:chrome, false) false else (opts[:hide_blocks_by_name] && ((opts[:block_name_hidden_match]&.present? && block.oname&.match(Regexp.new(opts[:block_name_hidden_match]))) || (opts[:block_name_include_match]&.present? && block.oname&.match(Regexp.new(opts[:block_name_include_match]))) || (opts[:block_name_wrapper_match]&.present? && block.oname&.match(Regexp.new(opts[:block_name_wrapper_match])))) && (block.oname&.present? || block[:label]&.present?) ) end end
def initialize(table = [])
- 
        table(Array) -- An array of hashes representing markdown sections.
def initialize(table = []) @table = table # &bc '@table.count:',@table.count end
def recursively_required(reqs)
- 
        (Array- An array of recursively required code block names.) 
Parameters:
- 
        reqs(Array) -- An array of requirements to start the recursion from.
def recursively_required(reqs) return [] unless reqs rem = reqs memo = [] while rem && rem.count.positive? rem = rem.map do |req| next if memo.include? req memo += [req] get_block_by_anyname(req).fetch(:reqs, []) end .compact .flatten(1) end memo end
def recursively_required_hash(source, memo = Hash.new([]))
- 
        (Hash)- A list of code blocks required by each source code block.
Parameters:
- 
        source(String) -- The name of the code block to start the recursion from.
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
def select_elements_with_neighbor_conditions(array,
def select_elements_with_neighbor_conditions(array, last_selected_placeholder = nil, next_selected_placeholder = nil) selected_elements = [] last_selected = last_selected_placeholder array.each_with_index do |current, index| next_element = if index < array.size - 1 array[index + 1] else next_selected_placeholder end if yield(last_selected, current, next_element) selected_elements << current last_selected = current end end selected_elements end