lib/mdoc.rb in markdown_exec-2.5.0 vs lib/mdoc.rb in markdown_exec-2.6.0

- old
+ new

@@ -2,10 +2,11 @@ # frozen_string_literal: true # encoding=utf-8 require_relative 'block_types' +require_relative 'collapser' require_relative 'filter' $pd = false unless defined?($pd) module MarkdownExec @@ -204,31 +205,118 @@ Filter.fcb_select? options, fcb_title_groups end ### hide rows correctly - unless options[:menu_include_imported_blocks] - selrows = selrows.reject do |block| - block.fetch(:depth, 0).positive? + unless opts[:menu_include_imported_blocks] + selrows = selrows.reject do |fcb| + fcb.fetch(:depth, 0).positive? end end if opts[:hide_blocks_by_name] - selrows = selrows.reject do |block| - hide_menu_block_on_name opts, block + selrows = selrows.reject do |fcb| + hide_menu_block_on_name(opts, fcb) end end + collapser = Collapser.new( + options: opts, state: opts[:compressed_ids] || {} + ) + selrows = collapser.reject(selrows, initialize: opts[:compressed_ids].nil?) do |fcb, hide, collapsed_level| + # update fcb per state + if fcb.collapsible + fcb.s1decorated = fcb.s1decorated + ' ' + (fcb.collapse ? opts[:menu_collapsible_symbol_collapsed] : opts[:menu_collapsible_symbol_expanded]) + end + end + opts[:compressed_ids] = collapser.state + # 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?) + [ + 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, + opts[:compressed_ids] + ] + end + + # Filters out blocks that are nested within a hierarchy of "hidden" blocks. + # + # The method iterates over selected rows and uses a callback block to determine if a given block + # should trigger hiding for subsequent blocks in the hierarchy. Once hiding is triggered, all + # blocks with a level greater than the triggering block's level are excluded until the next + # visible collapsible block. + # + # @param selrows [Array] The array of selected rows to filter based on the callback's results. + # @param callback [Lambda/Proc] Alternate to yield block. + # @param collapsible_types [Array] Types to select, nil for all. + # @yield [block] A callback to evaluate each block; should return true to initiate hiding. + # @return [Array] Filtered list of rows, excluding those hidden by hierarchical hiding rules. + # + # Example: + # proc_condition = Proc.new { |fcb| fcb.type == BlockType::HEADING && fcb.level == 1 } + # reject_collapsed_blocks(selrows, callback: proc_condition) + # + # lambda_condition = ->(fcb) { fcb.type == BlockType::HEADING && fcb.level == 1 } + # reject_collapsed_blocks(selrows, callback: lambda_condition) + # + # Block: Exits the enclosing method (test_with_block) if return is used. + # Flexible with arguments + # Proc: Exits the enclosing method (test_with_proc) when return is encountered, similar to a block. + # Lambda: Only exits the lambda itself, allowing reject_collapsed_blocks to continue execution. + # Stricter argument checking. + # + # **`lambda` provides more control** and avoids the early exit behavior caused by `return` in blocks or `Proc` objects, making it a safer choice when you want `reject_collapsed_blocks` to complete its execution regardless of the callback’s behavior. + def reject_collapsed_blocks( + selrows, + callback: nil, + reject_callback: nil, + collapsible_types: nil, + &block + ) + block ||= callback # Use callback if no block is provided + hiding = false # State to indicate if hiding is active + hidden_level = nil # Level at which hiding was triggered + + selrows.reject do |fcb| # Reject rows that should be hidden based on the hierarchy + if hiding + # Currently in hiding mode; evaluate if the current block should remain hidden + if collapsible_types.nil? || collapsible_types.include?(fcb.type) + if hidden_level.nil? + # No specific hidden level yet, allow the item to show + false + elsif fcb.level > hidden_level + reject_callback.call(fcb, ) if reject_callback + # The current block is at a deeper level and thus remains hidden + true + else + # At the same or higher level than hidden_level, check if the callback initiates hiding again + hiding = block.call(fcb) + hidden_level = fcb.level if hiding # Update hidden level if hiding continues + false # Do not hide the initiating block itself + end + else + reject_callback.call(fcb) if reject_callback + # Non-collapsible block types (e.g., text or note) continue hiding by default + true + end + + elsif block.call(fcb) + # If callback triggers hiding, initialize hiding state and hidden_level + hiding = fcb.type # Start hiding subsequent blocks + hidden_level = fcb.level # Define the hierarchical level for hiding + false # Do not hide the initiating block itself + + else + false # Default: do not hide if no hiding state + end 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`, @@ -414,33 +502,10 @@ end end selected_elements end - - # def select_elements_with_neighbor_conditions(array) - # # This function filters elements from the array where the current element has property A set to true - # # and both the previous and next elements have property B set to true. - # selected_elements = [] - - # array.each_with_index do |element, index| - # next if index.zero? # Skip the first element since it has no previous element - # break if index >= array.size - 1 # Break before the last to avoid out-of-bound errors - - # 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? - # selected_elements << element - # # else - # # pp 'SKIPPING', element - # end - # end - - # selected_elements - # end end end if $PROGRAM_NAME == __FILE__ require 'bundler/setup' @@ -538,10 +603,10 @@ assert result # this should be true based on the given logic end def test_fcbs_per_options opts = { hide_blocks_by_name: true, block_name_hidden_match: 'block1' } - result = @doc.fcbs_per_options(opts) + result, _ = @doc.fcbs_per_options(opts) assert_equal [@table[1], @table[2]], result end if false ### broken test def test_recursively_required result = @doc.recursively_required_hash('block3')