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:)

Returns:
  • (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 = {})

Returns:
  • (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,

Returns:
  • (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)

Returns:
  • (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 = {})

Returns:
  • (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 = {})

Returns:
  • (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)

Returns:
  • (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 = [])

Parameters:
  • table (Array) -- An array of hashes representing markdown sections.
def initialize(table = [])
  @table = table
  # &bc '@table.count:',@table.count
end

def recursively_required(reqs)

Returns:
  • (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([]))

Returns:
  • (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