lib/mdoc.rb in markdown_exec-2.1.0 vs lib/mdoc.rb in markdown_exec-2.2.0

- old
+ new

@@ -4,10 +4,12 @@ # encoding=utf-8 require_relative 'block_types' require_relative 'filter' +$pd = false unless defined?($pd) + module MarkdownExec ## # MDoc represents an imported markdown document. # # It provides methods to extract and manipulate specific sections @@ -21,15 +23,15 @@ # # @param table [Array<Hash>] An array of hashes representing markdown sections. # def initialize(table = []) @table = table - # &bc '@table.count:',@table.count + # &bt @table.count end def collect_block_code_cann(fcb) - body = fcb[:body].join("\n") + 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] @@ -42,19 +44,10 @@ else "#{yqcmd} > '#{mstdout[:name]}'" end end - 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 - # Collects and formats the shell command output to redirect script block code to a file or a variable. # # @param [Hash] fcb A hash containing information about the script block's stdout and body. # @option fcb [Hash] :stdout A hash specifying the stdout details. # @option stdout [Boolean] :type Indicates whether to export to a variable (true) or to write to a file (false). @@ -64,11 +57,11 @@ # @return [String] A string containing the formatted shell command to output the script block's body. # If stdout[:type] is true, the command will export the body to a shell variable. # If stdout[:type] is false, the command will write the body to a file. def collect_block_code_stdout(fcb) stdout = fcb[:stdout] - body = fcb[:body].join("\n") + 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" \ @@ -81,39 +74,42 @@ # @param name [String] The name of the code block to start the retrieval from. # @return [Array<Hash>] An array of code blocks required by the specified code block. # def collect_block_dependencies(anyname:) name_block = get_block_by_anyname(anyname) - raise "Named code block `#{anyname}` not found. (@#{__LINE__})" if name_block.nil? || name_block.keys.empty? + if name_block.nil? || name_block.keys.empty? + raise "Named code block `#{anyname}` not found. (@#{__LINE__})" + end nickname = name_block.pub_name dependencies = collect_dependencies(nickname) - # &bc 'dependencies.count:',dependencies.count + # &bt dependencies.count all_dependency_names = collect_unique_names(dependencies).push(nickname).uniq - # &bc 'all_dependency_names.count:',all_dependency_names.count + # &bt 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.pub_name) + # !fcb.fetch(:chrome, false) && all_dependency_names.include?(fcb.pub_name) + all_dependency_names.include?(fcb.pub_name) end - # &bc 'blocks.count:',blocks.count + # &bt 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.pub_name) # may not exist if block name is duplicated - if (call = fcb[:call]) + 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 + # &bt unmet_dependencies.count { all_dependency_names: all_dependency_names, blocks: blocks, dependencies: dependencies, unmet_dependencies: unmet_dependencies } @@ -127,33 +123,33 @@ 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 + # &bt blocks.count block_search.merge( { block_names: blocks.map(&:pub_name), 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] + elsif [BlockType::OPTS].include? fcb.shell + fcb.body # entire body is returned to requesing block + elsif [BlockType::LINK, + BlockType::VARS].include? fcb.shell nil - elsif fcb[:shell] == BlockType::PORT - collect_block_code_shell(fcb) + elsif fcb[:chrome] # for Link blocks like History + nil + elsif fcb.shell == BlockType::PORT + generate_env_variable_shell_commands(fcb) elsif label_body - block_name_for_bash_comment = fcb.pub_name.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 }))] + generate_label_body_code(fcb, block_source, label_format_above, + label_format_below) else # raw body - fcb[:body] + fcb.body end end.compact.flatten(1).compact } ) else block_search.merge({ block_names: [], code: [] }) @@ -220,25 +216,88 @@ # 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?) + !(current[:chrome] && !current.oname.present?) || + !(!prev_element.nil? && + prev_element.shell.present? && + !next_element.nil? && + next_element.shell.present?) end end + # Generates shell code lines to set environment variables named in the body of the given object. + # Reads a whitespace-separated list of environment variable names from `fcb.body`, + # retrieves their values from the current environment, and constructs shell commands + # to set these environment variables. + # + # @param fcb [Object] An object with a `body` method that returns an array of strings, + # where each string is a name of an environment variable. + # @return [Array<String>] An array of strings, each representing a shell command to + # set an environment variable in the format `KEY=value`. + # + # Example: + # If `fcb.body` returns ["PATH", "HOME"], and the current environment has PATH=/usr/bin + # and HOME=/home/user, this method will return: + # ["PATH=/usr/bin", "HOME=/home/user"] + # + def generate_env_variable_shell_commands(fcb) + fcb.body.join(' ').split.compact.map do |key| + "#{key}=#{Shellwords.escape ENV.fetch(key, '')}" + end + end + + # Generates a formatted code block with labels above and below the main content. + # The labels and content are based on the provided format strings and the body of the given object. + # + # @param fcb [Object] An object with a `pub_name` method that returns a string, and a `body` method that returns an array of strings. + # @param block_source [Hash] A hash containing additional information to be merged into the format strings. + # @param label_format_above [String, nil] A format string for the label above the content, or nil if no label is needed. + # @param label_format_below [String, nil] A format string for the label below the content, or nil if no label is needed. + # @return [Array<String>] An array of strings representing the formatted code block, with optional labels above and below the main content. + # + # Example: + # If `fcb.pub_name` returns "Example Block", `fcb.body` returns ["line1", "line2"], + # `block_source` is { source: "source_info" }, `label_format_above` is "Start of %{block_name}", + # and `label_format_below` is "End of %{block_name}", the method will return: + # ["Start of Example_Block", "line1", "line2", "End of Example_Block"] + # + def generate_label_body_code(fcb, block_source, label_format_above, label_format_below) + block_name_for_bash_comment = fcb.pub_name.gsub(/\s+/, '_') + + label_above = if label_format_above + format(label_format_above, + block_source.merge({ block_name: block_name_for_bash_comment })) + else + nil + end + label_below = if label_format_below + format(label_format_below, + block_source.merge({ block_name: block_name_for_bash_comment })) + else + nil + end + + [label_above, *fcb.body, label_below].compact + end + # Retrieves a code block by its name. # # @param name [String] The name of the code block to retrieve. # @param default [Hash] The default value to return if the code block is not found. # @return [Hash] The code block as a hash or the default value if not found. # def get_block_by_anyname(name, default = {}) + # &bt name @table.select do |fcb| - fcb.fetch(:nickname, - '') == name || fcb.fetch(:dname, '') == name || fcb.fetch(:oname, '') == name - end.fetch(0, default) + fcb.tap { |_ret| pp [__LINE__, 'get_block_by_anyname()', 'fcb', fcb] if $pd } + fcb.nickname == name || + fcb.dname == name || + fcb.oname == name || + fcb.pub_name == name + end.fetch(0, default).tap { |ret| pp [__LINE__, 'get_block_by_anyname() ->', ret] if $pd } end # Checks if a code block should be hidden based on the given options. # # @param opts [Hash] The options used for hiding code blocks. @@ -248,19 +307,19 @@ # :reek:UtilityFunction 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?) - ) + 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 # Recursively fetches required code blocks for a given list of requirements. # @@ -275,11 +334,11 @@ while rem && rem.count.positive? rem = rem.map do |req| next if memo.include? req memo += [req] - get_block_by_anyname(req).fetch(:reqs, []) + get_block_by_anyname(req).reqs end .compact .flatten(1) end memo @@ -293,13 +352,15 @@ 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) - raise "Named code block `#{source}` not found. (@#{__LINE__})" if block.nil? || block.keys.empty? + if block.nil? || block.keys.empty? + raise "Named code block `#{source}` not found. (@#{__LINE__})" + end - memo[source] = block[:reqs] + memo[source] = block.reqs return memo unless memo[source]&.count&.positive? memo[source].each do |req| next if memo.keys.include? req @@ -320,15 +381,15 @@ raise "Named code block `#{source}` not found. (@#{__LINE__})" end - return memo unless block[:reqs] + return memo unless block.reqs - memo[source] = block[:reqs] + memo[source] = block.reqs - block[:reqs].each { |req| collect_dependencies(req, memo) unless memo.key?(req) } + 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) @@ -362,11 +423,11 @@ # prev_element = array[index - 1] # next_element = array[index + 1] # # Check the conditions for property A on the current element and property B on adjacent elements - # unless element[:chrome] && !element[:oname].present? && prev_element[:shell].present? && next_element[:shell].present? + # unless element[:chrome] && !element[:oname].present? && prev_element.shell.present? && next_element.shell.present? # selected_elements << element # # else # # pp 'SKIPPING', element # end # end @@ -509,15 +570,9 @@ end def test_collect_wrapped_blocks # Test case 1: blocks with wraps OpenStruct.new(oname: 'block1') - - assert_equal(%w[{wrap1} a], - @mdoc.collect_wrapped_blocks( - [OpenStruct.new(oname: 'a', - wraps: ['{wrap1}'])] - ).map(&:oname)) assert_equal(%w[{wrap2-before} {wrap2} b {wrap2-after}], @mdoc.collect_wrapped_blocks( [OpenStruct.new(oname: 'b', wraps: ['{wrap2}'])]