lib/hash_delegator.rb in markdown_exec-1.8.6 vs lib/hash_delegator.rb in markdown_exec-1.8.7
- old
+ new
@@ -35,10 +35,277 @@
def non_empty?
!empty?
end
end
+module HashDelegatorSelf
+ # def add_back_option(menu_blocks)
+ # append_chrome_block(menu_blocks, MenuState::BACK)
+ # end
+
+ # Applies an ANSI color method to a string using a specified color key.
+ # The method retrieves the color method from the provided hash. If the color key
+ # is not present in the hash, it uses a default color method.
+ # @param string [String] The string to be colored.
+ # @param color_methods [Hash] A hash where keys are color names (String/Symbol) and values are color methods.
+ # @param color_key [String, Symbol] The key representing the desired color method in the color_methods hash.
+ # @param default_method [String] (optional) Default color method to use if color_key is not found in color_methods. Defaults to 'plain'.
+ # @return [String] The colored string.
+ def apply_color_from_hash(string, color_methods, color_key, default_method: 'plain')
+ color_method = color_methods.fetch(color_key, default_method).to_sym
+ string.to_s.send(color_method)
+ end
+
+ # # Enhanced `apply_color_from_hash` method to support dynamic color transformations
+ # # @param string [String] The string to be colored.
+ # # @param color_transformations [Hash] A hash mapping color names to lambdas that apply color transformations.
+ # # @param color_key [String, Symbol] The key representing the desired color transformation in the color_transformations hash.
+ # # @param default_transformation [Proc] Default color transformation to use if color_key is not found in color_transformations.
+ # # @return [String] The colored string.
+ # def apply_color_from_hash(string, color_transformations, color_key, default_transformation: ->(str) { str })
+ # transformation = color_transformations.fetch(color_key.to_sym, default_transformation)
+ # transformation.call(string)
+ # end
+ # color_transformations = {
+ # red: ->(str) { "\e[31m#{str}\e[0m" }, # ANSI color code for red
+ # green: ->(str) { "\e[32m#{str}\e[0m" }, # ANSI color code for green
+ # # Add more color transformations as needed
+ # }
+ # string = "Hello, World!"
+ # colored_string = apply_color_from_hash(string, color_transformations, :red)
+ # puts colored_string # This will print the string in red
+
+ # Searches for the first element in a collection where the specified key matches a given value.
+ # This method is particularly useful for finding a specific hash-like object within an enumerable collection.
+ # If no match is found, it returns a specified default value.
+ #
+ # @param blocks [Enumerable] The collection of hash-like objects to search.
+ # @param key [Object] The key to search for in each element of the collection.
+ # @param value [Object] The value to match against each element's corresponding key value.
+ # @param default [Object, nil] The default value to return if no match is found (optional).
+ # @return [Object, nil] The first matching element or the default value if no match is found.
+ def block_find(blocks, key, value, default = nil)
+ blocks.find { |item| item[key] == value } || default
+ end
+
+ def code_merge(*bodies)
+ merge_lists(*bodies)
+ end
+
+ def count_matches_in_lines(lines, regex)
+ lines.count { |line| line.to_s.match(regex) }
+ end
+
+ def create_directory_for_file(file_path)
+ FileUtils.mkdir_p(File.dirname(file_path))
+ end
+
+ # Creates a file at the specified path, writes the given content to it,
+ # and sets file permissions if required. Handles any errors encountered during the process.
+ #
+ # @param file_path [String] The path where the file will be created.
+ # @param content [String] The content to write into the file.
+ # @param chmod_value [Integer] The file permission value to set; skips if zero.
+ def create_file_and_write_string_with_permissions(file_path, content,
+ chmod_value)
+ create_directory_for_file(file_path)
+ File.write(file_path, content)
+ set_file_permissions(file_path, chmod_value) unless chmod_value.zero?
+ rescue StandardError
+ error_handler('create_file_and_write_string_with_permissions')
+ end
+
+ # def create_temp_file
+ # Dir::Tmpname.create(self.class.to_s) { |path| path }
+ # end
+
+ # Updates the title of an FCB object from its body content if the title is nil or empty.
+ def default_block_title_from_body(fcb)
+ return unless fcb.title.nil? || fcb.title.empty?
+
+ fcb.derive_title_from_body
+ end
+
+ # delete the current line if it is empty and the previous is also empty
+ def delete_consecutive_blank_lines!(blocks_menu)
+ blocks_menu.process_and_conditionally_delete! do |prev_item, current_item, _next_item|
+ prev_item&.fetch(:chrome, nil) && !prev_item&.fetch(:oname).present? &&
+ current_item&.fetch(:chrome, nil) && !current_item&.fetch(:oname).present?
+ end
+ end
+
+ # # Deletes a temporary file specified by an environment variable.
+ # # Checks if the file exists before attempting to delete it and clears the environment variable afterward.
+ # # Any errors encountered during deletion are handled gracefully.
+ # def delete_required_temp_file(temp_blocks_file_path)
+ # return if temp_blocks_file_path.nil? || temp_blocks_file_path.empty?
+
+ # HashDelegator.remove_file_without_standard_errors(temp_blocks_file_path)
+ # end
+
+ def error_handler(name = '', opts = {})
+ Exceptions.error_handler(
+ "HashDelegator.#{name} -- #{$!}",
+ opts
+ )
+ end
+
+ # # DebugHelper.d ["HDmm method_name: #{method_name}", "#{first_n_caller_items 1}"]
+ # def first_n_caller_items(n)
+ # call_stack = caller
+ # base_path = File.realpath('.')
+
+ # # Modify the call stack to remove the base path and keep only the first n items
+ # call_stack.take(n + 1)[1..].map do |line|
+ # " . #{line.sub(/^#{Regexp.escape(base_path)}\//, '')}"
+ # end.join("\n")
+ # end
+
+ # Formats and returns the execution streams (like stdin, stdout, stderr) for a given key.
+ # It concatenates the array of strings found under the specified key in the run_state's files.
+ #
+ # @param key [Symbol] The key corresponding to the desired execution stream.
+ # @return [String] A concatenated string of the execution stream's contents.
+ def format_execution_streams(key, files = {})
+ (files || {}).fetch(key, []).join
+ end
+
+ # Indents all lines in a given string with a specified indentation string.
+ # @param body [String] A multi-line string to be indented.
+ # @param indent [String] The string used for indentation (default is an empty string).
+ # @return [String] A single string with each line indented as specified.
+ def indent_all_lines(body, indent = nil)
+ return body unless indent&.non_empty?
+
+ body.lines.map { |line| indent + line.chomp }.join("\n")
+ end
+
+ def initialize_fcb_names(fcb)
+ fcb.oname = fcb.dname = fcb.title || ''
+ end
+
+ def merge_lists(*args)
+ # Filters out nil values, flattens the arrays, and ensures an empty list is returned if no valid lists are provided
+ merged = args.compact.flatten
+ merged.empty? ? [] : merged
+ end
+
+ def next_link_state(block_name_from_cli, was_using_cli, block_state)
+ # &bsp 'next_link_state', block_name_from_cli, was_using_cli, block_state
+ # Set block_name based on block_name_from_cli
+ block_name = block_name_from_cli ? @cli_block_name : nil
+ # &bsp 'block_name:', block_name
+
+ # Determine the state of breaker based on was_using_cli and the block type
+ breaker = !block_name && !block_name_from_cli && was_using_cli && block_state.block[:shell] == BlockType::BASH
+ # &bsp 'breaker:', breaker
+
+ # Reset block_name_from_cli if the conditions are not met
+ block_name_from_cli ||= false
+ # &bsp 'block_name_from_cli:', block_name_from_cli
+
+ [block_name, block_name_from_cli, breaker]
+ end
+
+ def parse_yaml_data_from_body(body)
+ body.any? ? YAML.load(body.join("\n")) : {}
+ end
+
+ # Reads required code blocks from a temporary file specified by an environment variable.
+ # @return [Array<String>] Lines read from the temporary file, or an empty array if file is not found or path is empty.
+ def read_required_blocks_from_temp_file(temp_blocks_file_path)
+ return [] if temp_blocks_file_path.to_s.empty?
+
+ if File.exist?(temp_blocks_file_path)
+ File.readlines(
+ temp_blocks_file_path, chomp: true
+ )
+ else
+ []
+ end
+ end
+
+ def remove_file_without_standard_errors(path)
+ FileUtils.rm_f(path)
+ end
+
+ # Evaluates the given string as Ruby code and rescues any StandardErrors.
+ # If an error occurs, it calls the error_handler method with 'safeval'.
+ # @param str [String] The string to be evaluated.
+ # @return [Object] The result of evaluating the string.
+ def safeval(str)
+ eval(str)
+ rescue StandardError # catches NameError, StandardError
+ error_handler('safeval')
+ end
+
+ def set_file_permissions(file_path, chmod_value)
+ File.chmod(chmod_value, file_path)
+ end
+
+ # Creates a TTY prompt with custom settings. Specifically, it disables the default 'cross' symbol and
+ # defines a lambda function to handle interrupts.
+ # @return [TTY::Prompt] A new TTY::Prompt instance with specified configurations.
+ def tty_prompt_without_disabled_symbol
+ TTY::Prompt.new(
+ interrupt: lambda {
+ puts
+ raise TTY::Reader::InputInterrupt
+ },
+ symbols: { cross: ' ' }
+ )
+ end
+
+ # Updates the attributes of the given fcb object and conditionally yields to a block.
+ # It initializes fcb names and sets the default block title from fcb's body.
+ # If the fcb has a body and meets certain conditions, it yields to the given block.
+ #
+ # @param fcb [Object] The fcb object whose attributes are to be updated.
+ # @param selected_messages [Array<Symbol>] A list of message types to determine if yielding is applicable.
+ # @param block [Block] An optional block to yield to if conditions are met.
+ def update_menu_attrib_yield_selected(fcb, selected_messages, configuration = {}, &block)
+ initialize_fcb_names(fcb)
+ return unless fcb.body
+
+ default_block_title_from_body(fcb)
+ MarkdownExec::Filter.yield_to_block_if_applicable(fcb, selected_messages, configuration,
+ &block)
+ end
+
+ # Writes the provided code blocks to a file.
+ # @param code_blocks [String] Code blocks to write into the file.
+ def write_code_to_file(content, path)
+ File.write(path, content)
+ end
+
+ def write_execution_output_to_file(files, filespec)
+ FileUtils.mkdir_p File.dirname(filespec)
+
+ File.write(
+ filespec,
+ ["-STDOUT-\n",
+ format_execution_streams(ExecutionStreams::StdOut, files),
+ "-STDERR-\n",
+ format_execution_streams(ExecutionStreams::StdErr, files),
+ "-STDIN-\n",
+ format_execution_streams(ExecutionStreams::StdIn, files),
+ "\n"].join
+ )
+ end
+
+ # Yields a line as a new block if the selected message type includes :line.
+ # @param [String] line The line to be processed.
+ # @param [Array<Symbol>] selected_messages A list of message types to check.
+ # @param [Proc] block The block to be called with the line data.
+ def yield_line_if_selected(line, selected_messages, &block)
+ return unless block && selected_messages.include?(:line)
+
+ block.call(:line, MarkdownExec::FCB.new(body: [line]))
+ end
+end
+### require_relative 'hash_delegator_self'
+
# This module provides methods for compacting and converting data structures.
module CompactionHelpers
# Converts an array of key-value pairs into a hash, applying compaction to the values.
# Each value is processed by `compact_hash` to remove ineligible elements.
#
@@ -104,15 +371,16 @@
end
class HashDelegator
attr_accessor :most_recent_loaded_filename, :pass_args, :run_state
+ extend HashDelegatorSelf
include CompactionHelpers
def initialize(delegate_object = {})
@delegate_object = delegate_object
- @prompt = tty_prompt_without_disabled_symbol
+ @prompt = HashDelegator.tty_prompt_without_disabled_symbol
@most_recent_loaded_filename = nil
@pass_args = []
@run_state = OpenStruct.new(
link_history: []
@@ -136,31 +404,44 @@
# Modifies the provided menu blocks array by adding 'Back' and 'Exit' options,
# along with initial and final dividers, based on the delegate object's configuration.
#
# @param menu_blocks [Array] The array of menu block elements to be modified.
- def add_menu_chrome_blocks!(menu_blocks)
+ def add_menu_chrome_blocks!(menu_blocks, link_state)
return unless @delegate_object[:menu_link_format].present?
+ if @delegate_object[:menu_with_inherited_lines]
+ add_inherited_lines(menu_blocks,
+ link_state)
+ end
+
+ # back before exit
add_back_option(menu_blocks) if should_add_back_option?
+
+ # exit after other options
add_exit_option(menu_blocks) if @delegate_object[:menu_with_exit]
+
add_dividers(menu_blocks)
end
private
def add_back_option(menu_blocks)
append_chrome_block(menu_blocks, MenuState::BACK)
end
+ def add_dividers(menu_blocks)
+ append_divider(menu_blocks, :initial)
+ append_divider(menu_blocks, :final)
+ end
+
def add_exit_option(menu_blocks)
append_chrome_block(menu_blocks, MenuState::EXIT)
end
- def add_dividers(menu_blocks)
- append_divider(menu_blocks, :initial)
- append_divider(menu_blocks, :final)
+ def add_inherited_lines(menu_blocks, link_state)
+ append_inherited_lines(menu_blocks, link_state)
end
public
# Appends a chrome block, which is a menu option for Back or Exit
@@ -177,11 +458,11 @@
option_name = @delegate_object[:menu_option_exit_name]
insert_at_top = @delegate_object[:menu_exit_at_top]
end
formatted_name = format(@delegate_object[:menu_link_format],
- safeval(option_name))
+ HashDelegator.safeval(option_name))
chrome_block = FCB.new(
chrome: true,
dname: HashDelegator.new(@delegate_object).string_send_color(
formatted_name, :menu_link_color
),
@@ -198,10 +479,43 @@
# Appends a formatted divider to the specified position in a menu block array.
# The method checks for the presence of formatting options before appending.
#
# @param menu_blocks [Array] The array of menu block elements.
# @param position [Symbol] The position to insert the divider (:initial or :final).
+ def append_inherited_lines(menu_blocks, link_state, position: top)
+ return unless link_state.inherited_lines.present?
+
+ insert_at_top = @delegate_object[:menu_inherited_lines_at_top]
+ chrome_blocks = link_state.inherited_lines.map do |line|
+ formatted = format(@delegate_object[:menu_inherited_lines_format],
+ { line: line })
+ FCB.new(
+ chrome: true,
+ disabled: '',
+ dname: HashDelegator.new(@delegate_object).string_send_color(
+ formatted, :menu_inherited_lines_color
+ ),
+ oname: formatted
+ )
+ end
+
+ if insert_at_top
+ # Prepend an array of elements to the beginning
+ menu_blocks.unshift(*chrome_blocks)
+ else
+ # Append an array of elements to the end
+ menu_blocks.concat(chrome_blocks)
+ end
+ rescue StandardError
+ HashDelegator.error_handler('append_inherited_lines')
+ end
+
+ # Appends a formatted divider to the specified position in a menu block array.
+ # The method checks for the presence of formatting options before appending.
+ #
+ # @param menu_blocks [Array] The array of menu block elements.
+ # @param position [Symbol] The position to insert the divider (:initial or :final).
def append_divider(menu_blocks, position)
return unless divider_formatting_present?(position)
divider = create_divider(position)
position == :initial ? menu_blocks.unshift(divider) : menu_blocks.push(divider)
@@ -222,35 +536,23 @@
end
end
# private
- # Searches for the first element in a collection where the specified key matches a given value.
- # This method is particularly useful for finding a specific hash-like object within an enumerable collection.
- # If no match is found, it returns a specified default value.
- #
- # @param blocks [Enumerable] The collection of hash-like objects to search.
- # @param key [Object] The key to search for in each element of the collection.
- # @param value [Object] The value to match against each element's corresponding key value.
- # @param default [Object, nil] The default value to return if no match is found (optional).
- # @return [Object, nil] The first matching element or the default value if no match is found.
- def block_find(blocks, key, value, default = nil)
- blocks.find { |item| item[key] == value } || default
- end
-
# Iterates through nested files to collect various types of blocks, including dividers, tasks, and others.
# The method categorizes blocks based on their type and processes them accordingly.
#
# @return [Array<FCB>] An array of FCB objects representing the blocks.
def blocks_from_nested_files
blocks = []
iter_blocks_from_nested_files do |btype, fcb|
process_block_based_on_type(blocks, btype, fcb)
end
+ # &bc 'blocks.count:', blocks.count
blocks
rescue StandardError
- error_handler('blocks_from_nested_files')
+ HashDelegator.error_handler('blocks_from_nested_files')
end
# private
def cfile
@@ -271,19 +573,10 @@
return false
end
true
end
- def code_join(*bodies)
- bc = bodies&.compact
- bc.count.positive? ? bc.join("\n") : nil
- end
-
- def code_merge(*bodies)
- merge_lists(*bodies)
- end
-
# Collects required code lines based on the selected block and the delegate object's configuration.
# If the block type is VARS, it also sets environment variables based on the block's content.
#
# @param mdoc [YourMDocClass] An instance of the MDoc class.
# @param selected [Hash] The selected block.
@@ -310,11 +603,11 @@
elsif true
warn format_and_highlight_dependencies(dependencies,
highlight: [@delegate_object[:block_name]])
end
- code_merge link_state&.inherited_lines, required[:code]
+ HashDelegator.code_merge(link_state&.inherited_lines, required[:code])
end
def command_execute(command, args: [])
@run_state.files = Hash.new([])
@run_state.options = @delegate_object
@@ -361,21 +654,21 @@
def load_cli_or_user_selected_block(all_blocks, menu_blocks, default)
if @delegate_object[:block_name].present?
block = all_blocks.find do |item|
item[:oname] == @delegate_object[:block_name]
- end
+ end&.merge(block_name_from_ui: false)
else
block_state = wait_for_user_selected_block(all_blocks, menu_blocks,
default)
- block = block_state.block
+ block = block_state.block&.merge(block_name_from_ui: true)
state = block_state.state
end
SelectedBlockMenuState.new(block, state)
rescue StandardError
- error_handler('load_cli_or_user_selected_block')
+ HashDelegator.error_handler('load_cli_or_user_selected_block')
end
# This method is responsible for handling the execution of generic blocks in a markdown document.
# It collects the required code lines from the document and, depending on the configuration,
# may display the code for user approval before execution. It then executes the approved block.
@@ -410,21 +703,13 @@
#
# @return [Integer] The count of fenced code blocks in the file.
def count_blocks_in_filename
regex = Regexp.new(@delegate_object[:fenced_start_and_end_regex])
lines = cfile.readlines(@delegate_object[:filename])
- count_matches_in_lines(lines, regex) / 2
+ HashDelegator.count_matches_in_lines(lines, regex) / 2
end
- # private
-
- def count_matches_in_lines(lines, regex)
- lines.count { |line| line.to_s.match(regex) }
- end
-
- # private
-
##
# Creates and adds a formatted block to the blocks array based on the provided match and format options.
# @param blocks [Array] The array of blocks to add the new block to.
# @param match_data [MatchData] The match data containing named captures for formatting.
# @param format_option [String] The format string to be used for the new block.
@@ -447,16 +732,17 @@
# @param fcb [FCB] The file control block being processed.
# @param opts [Hash] Options containing configuration for line processing.
# @param use_chrome [Boolean] Indicates if the chrome styling should be applied.
def create_and_add_chrome_blocks(blocks, fcb)
match_criteria = [
- { match: :menu_task_match, format: :menu_task_format,
- color: :menu_task_color },
+ { match: :heading1_match, format: :menu_heading1_format, color: :menu_heading1_color },
+ { match: :heading2_match, format: :menu_heading2_format, color: :menu_heading2_color },
+ { match: :heading3_match, format: :menu_heading3_format, color: :menu_heading3_color },
{ match: :menu_divider_match, format: :menu_divider_format,
color: :menu_divider_color },
- { match: :menu_note_match, format: :menu_note_format,
- color: :menu_note_color }
+ { match: :menu_note_match, format: :menu_note_format, color: :menu_note_color },
+ { match: :menu_task_match, format: :menu_task_format, color: :menu_task_color }
]
match_criteria.each do |criteria|
unless @delegate_object[criteria[:match]].present? &&
(mbody = fcb.body[0].match @delegate_object[criteria[:match]])
next
@@ -466,76 +752,23 @@
@delegate_object[criteria[:color]].to_sym)
break
end
end
- # Creates a file at the specified path, writes the given content to it,
- # and sets file permissions if required. Handles any errors encountered during the process.
- #
- # @param file_path [String] The path where the file will be created.
- # @param content [String] The content to write into the file.
- # @param chmod_value [Integer] The file permission value to set; skips if zero.
- def create_file_and_write_string_with_permissions(file_path, content,
- chmod_value)
- create_directory_for_file(file_path)
- File.write(file_path, content)
- set_file_permissions(file_path, chmod_value) unless chmod_value.zero?
- rescue StandardError
- error_handler('create_file_and_write_string_with_permissions')
- end
-
- # private
-
- def create_directory_for_file(file_path)
- FileUtils.mkdir_p(File.dirname(file_path))
- end
-
def create_divider(position)
divider_key = position == :initial ? :menu_initial_divider : :menu_final_divider
oname = format(@delegate_object[:menu_divider_format],
- safeval(@delegate_object[divider_key]))
+ HashDelegator.safeval(@delegate_object[divider_key]))
FCB.new(
chrome: true,
disabled: '',
dname: string_send_color(oname, :menu_divider_color),
oname: oname
)
end
- # private
-
- def create_temp_file
- Dir::Tmpname.create(self.class.to_s) { |path| path }
- end
-
- # Updates the title of an FCB object from its body content if the title is nil or empty.
- def default_block_title_from_body(fcb)
- return unless fcb.title.nil? || fcb.title.empty?
-
- fcb.derive_title_from_body
- end
-
- # delete the current line if it is empty and the previous is also empty
- def delete_consecutive_blank_lines!(blocks_menu)
- blocks_menu.process_and_conditionally_delete! do |prev_item, current_item, _next_item|
- prev_item&.fetch(:chrome, nil) && !prev_item&.fetch(:oname).present? &&
- current_item&.fetch(:chrome, nil) && !current_item&.fetch(:oname).present?
- end
- end
-
- # Deletes a temporary file specified by an environment variable.
- # Checks if the file exists before attempting to delete it and clears the environment variable afterward.
- # Any errors encountered during deletion are handled gracefully.
- def delete_required_temp_file(temp_blocks_file_path)
- return if temp_blocks_file_path.nil? || temp_blocks_file_path.empty?
-
- safely_remove_file(temp_blocks_file_path)
- rescue StandardError
- error_handler('delete_required_temp_file')
- end
-
# Determines the state of a selected block in the menu based on the selected option.
# It categorizes the selected option into either EXIT, BACK, or CONTINUE state.
#
# @param selected_option [Hash] The selected menu option.
# @return [SelectedBlockMenuState] An object representing the state of the selected block.
@@ -568,28 +801,19 @@
def divider_formatting_present?(position)
divider_key = position == :initial ? :menu_initial_divider : :menu_final_divider
@delegate_object[:menu_divider_format].present? && @delegate_object[divider_key].present?
end
- def error_handler(name = '', opts = {})
- Exceptions.error_handler(
- "HashDelegator.#{name} -- #{$!}",
- opts
- )
- end
-
- # public
-
# Executes a block of code that has been approved for execution.
# It sets the script block name, writes command files if required, and handles the execution
# including output formatting and summarization.
#
# @param required_lines [Array<String>] The lines of code to be executed.
# @param selected [FCB] The selected functional code block object.
def execute_required_lines(required_lines = [])
- # set_script_block_name(selected)
- save_executed_script_if_specified(required_lines)
+ # @run_state.script_block_name = selected[:oname]
+ write_command_file(required_lines) if @delegate_object[:save_executed_script]
format_and_execute_command(required_lines)
post_execution_process
end
# Execute a code block after approval and provide user interaction options.
@@ -634,21 +858,10 @@
color_sym: :execution_report_preview_frame_color)
data_string = @delegate_object.fetch(data_sym, default).to_s
string_send_color(data_string, color_sym)
end
- # DebugHelper.d ["HDmm method_name: #{method_name}", "#{first_n_caller_items 1}"]
- def first_n_caller_items(n)
- call_stack = caller
- base_path = File.realpath('.')
-
- # Modify the call stack to remove the base path and keep only the first n items
- call_stack.take(n + 1)[1..].map do |line|
- " . #{line.sub(/^#{Regexp.escape(base_path)}\//, '')}"
- end.join("\n")
- end
-
def format_and_execute_command(lines)
formatted_command = lines.flatten.join("\n")
@fout.fout fetch_color(data_sym: :script_execution_head,
color_sym: :script_execution_frame_color)
command_execute(formatted_command, args: @pass_args)
@@ -670,20 +883,10 @@
formatted_string = format(@delegate_object.fetch(format_sym, ''),
context).to_s
string_send_color(formatted_string, color_sym)
end
- # Formats and returns the execution streams (like stdin, stdout, stderr) for a given key.
- # It concatenates the array of strings found under the specified key in the run_state's files.
- #
- # @param key [Symbol] The key corresponding to the desired execution stream.
- # @return [String] A concatenated string of the execution stream's contents.
- def format_execution_streams(key)
- files = @run_state.files || {}
- files.fetch(key, []).join
- end
-
# Processes a block to generate its summary, modifying its attributes based on various matching criteria.
# It handles special formatting for bash blocks, extracting and setting properties like call, stdin, stdout, and dname.
#
# @param fcb [Object] An object representing a functional code block.
# @return [Object] The modified functional code block with updated summary attributes.
@@ -709,11 +912,11 @@
# Updates the delegate object's state based on the provided block state.
# It sets the block name and determines if the user clicked the back link in the menu.
#
# @param block_state [Object] An object representing the state of a block in the menu.
- def handle_block_state(block_state)
+ def handle_back_or_continue(block_state)
return if block_state.nil?
unless [MenuState::BACK,
MenuState::CONTINUE].include?(block_state.state)
return
end
@@ -742,20 +945,10 @@
@process_cv.signal
end
end
end
- # Indents all lines in a given string with a specified indentation string.
- # @param body [String] A multi-line string to be indented.
- # @param indent [String] The string used for indentation (default is an empty string).
- # @return [String] A single string with each line indented as specified.
- def indent_all_lines(body, indent = nil)
- return body unless indent&.non_empty?
-
- body.lines.map { |line| indent + line.chomp }.join("\n")
- end
-
# Initializes variables for regex and other states
def initial_state
{
fenced_start_and_end_regex: Regexp.new(@delegate_object.fetch(
:fenced_start_and_end_regex, '^(?<indent> *)`{3,}'
@@ -782,17 +975,14 @@
@logged_stdout_filespec =
@delegate_object[:logged_stdout_filespec] =
File.join @delegate_object[:saved_stdout_folder],
@delegate_object[:logged_stdout_filename]
@logged_stdout_filespec = @delegate_object[:logged_stdout_filespec]
- write_execution_output_to_file
+ HashDelegator.write_execution_output_to_file(@run_state.files,
+ @delegate_object[:logged_stdout_filespec])
end
- def initialize_fcb_names(fcb)
- fcb.oname = fcb.dname = fcb.title || ''
- end
-
# Iterates through blocks in a file, applying the provided block to each line.
# The iteration only occurs if the file exists.
# @yield [Symbol] :filter Yields to obtain selected messages for processing.
def iter_blocks_from_nested_files(&block)
return unless check_file_existence(@delegate_object[:filename])
@@ -843,11 +1033,11 @@
block_name = @delegate_object[:document_load_opts_block_name]
unless block_name.present? && @most_recent_loaded_filename != @delegate_object[:filename]
return
end
- block = block_find(all_blocks, :oname, block_name)
+ block = HashDelegator.block_find(all_blocks, :oname, block_name)
return unless block
options_state = read_show_options_and_trigger_reuse(block)
@menu_base_options.merge!(options_state.options)
@delegate_object.merge!(options_state.options)
@@ -864,20 +1054,21 @@
[menu_blocks, mdoc]
end
## Handles the file loading and returns the blocks in the file and MDoc instance
#
- def mdoc_menu_and_blocks_from_nested_files
+ def mdoc_menu_and_blocks_from_nested_files(link_state)
all_blocks, mdoc = mdoc_and_blocks_from_nested_files
# recreate menu with new options
#
all_blocks, mdoc = mdoc_and_blocks_from_nested_files if load_auto_blocks(all_blocks)
menu_blocks = mdoc.fcbs_per_options(@delegate_object)
- add_menu_chrome_blocks!(menu_blocks)
- delete_consecutive_blank_lines!(menu_blocks) if true ### compress empty lines
+ add_menu_chrome_blocks!(menu_blocks, link_state)
+ ### compress empty lines
+ HashDelegator.delete_consecutive_blank_lines!(menu_blocks) if true
[all_blocks, menu_blocks, mdoc]
end
# Formats and optionally colors a menu option based on delegate object's configuration.
# @param option_symbol [Symbol] The symbol key for the menu option in the delegate object.
@@ -892,25 +1083,19 @@
# Formats a menu option based on the delegate object's configuration.
# It safely evaluates the value of the option and optionally formats it.
# @param option_symbol [Symbol] The symbol key for the menu option in the delegate object.
# @return [String] The formatted or original value of the menu option.
def menu_chrome_formatted_option(option_symbol = :menu_option_back_name)
- option_value = safeval(@delegate_object.fetch(option_symbol, ''))
+ option_value = HashDelegator.safeval(@delegate_object.fetch(option_symbol, ''))
if @delegate_object[:menu_chrome_format]
format(@delegate_object[:menu_chrome_format], option_value)
else
option_value
end
end
- def merge_lists(*args)
- # Filters out nil values, flattens the arrays, and ensures an empty list is returned if no valid lists are provided
- merged = args.compact.flatten
- merged.empty? ? [] : merged
- end
-
# If a method is missing, treat it as a key for the @delegate_object.
def method_missing(method_name, *args, &block)
if @delegate_object.respond_to?(method_name)
@delegate_object.send(method_name, *args, &block)
elsif method_name.to_s.end_with?('=') && args.size == 1
@@ -919,21 +1104,17 @@
@delegate_object[method_name]
# super
end
end
- def shift_cli_argument!
- return false unless @menu_base_options[:input_cli_rest].present?
+ def shift_cli_argument
+ return true unless @menu_base_options[:input_cli_rest].present?
@cli_block_name = @menu_base_options[:input_cli_rest].shift
- # @delegate_object[:input_cli_rest].shift
- # p [__LINE__, @cli_block_name, @menu_base_options[:input_cli_rest]]
- true
+ false
end
- # private
-
def output_color_formatted(data_sym, color_sym)
formatted_string = string_send_color(@delegate_object[data_sym],
color_sym)
@fout.fout formatted_string
end
@@ -982,33 +1163,27 @@
:output_execution_label_value_color) },
format_sym: :output_execution_label_format
), level: level
end
- # private
-
- def parse_yaml_data_from_body(body)
- body.any? ? YAML.load(body.join("\n")) : {}
- end
-
def pop_add_current_code_to_head_and_trigger_load(_link_state, block_names, code_lines,
dependencies)
pop = @link_history.pop # updatable
- next_link_state = LinkState.new(
+ next_state = LinkState.new(
block_name: pop.block_name,
document_filename: pop.document_filename,
inherited_block_names:
(pop.inherited_block_names + block_names).sort.uniq,
inherited_dependencies:
dependencies.merge(pop.inherited_dependencies || {}), ### merge, not replace, key data
inherited_lines:
- code_merge(pop.inherited_lines, code_lines)
+ HashDelegator.code_merge(pop.inherited_lines, code_lines)
)
- @link_history.push(next_link_state)
+ @link_history.push(next_state)
- next_link_state.block_name = nil
- LoadFileLinkState.new(LoadFile::Load, next_link_state)
+ next_state.block_name = nil
+ LoadFileLinkState.new(LoadFile::Load, next_state)
end
# This method handles the back-link operation in the Markdown execution context.
# It updates the history state and prepares to load the next block.
#
@@ -1039,11 +1214,11 @@
menu_blocks.map do |fcb|
next if Filter.prepared_not_in_menu?(@delegate_object, fcb,
%i[block_name_include_match block_name_wrapper_match])
fcb.merge!(
- name: indent_all_lines(fcb.dname, fcb.fetch(:indent, nil)),
+ name: HashDelegator.indent_all_lines(fcb.dname, fcb.fetch(:indent, nil)),
label: BlockLabel.make(
body: fcb[:body],
filename: @delegate_object[:filename],
headings: fcb.fetch(:headings, []),
menu_blocks_with_docname: @delegate_object[:menu_blocks_with_docname],
@@ -1069,14 +1244,11 @@
when :blocks
blocks.push(get_block_summary(fcb))
when :filter
%i[blocks line]
when :line
- unless @delegate_object[:no_chrome]
- create_and_add_chrome_blocks(blocks,
- fcb)
- end
+ create_and_add_chrome_blocks(blocks, fcb) unless @delegate_object[:no_chrome]
end
end
##
# Presents a menu to the user for approving an action and performs additional tasks based on the selection.
@@ -1146,11 +1318,11 @@
# @param mdoc [Object] Markdown document object.
# @param selected [FCB] Selected code block.
# @return [LoadFileLinkState] Object indicating the next action for file loading.
def push_link_history_and_trigger_load(link_block_body, mdoc, selected,
link_state = LinkState.new)
- link_block_data = parse_yaml_data_from_body(link_block_body)
+ link_block_data = HashDelegator.parse_yaml_data_from_body(link_block_body)
# load key and values from link block into current environment
#
(link_block_data['vars'] || []).each do |(key, value)|
ENV[key] = value.to_s
@@ -1176,11 +1348,11 @@
next_document_filename = link_block_data['file'] || @delegate_object[:filename]
# if an eval link block, evaluate code_lines and return its standard output
#
if link_block_data.fetch('eval', false)
- all_code = code_merge(link_state&.inherited_lines, code_lines)
+ all_code = HashDelegator.code_merge(link_state&.inherited_lines, code_lines)
output = `#{all_code.join("\n")}`.split("\n")
label_format_above = @delegate_object[:shell_code_label_format_above]
label_format_below = @delegate_object[:shell_code_label_format_below]
block_source = { document_filename: link_state&.document_filename }
@@ -1205,32 +1377,18 @@
link_history_push_and_next(
curr_block_name: selected[:oname],
curr_document_filename: @delegate_object[:filename],
inherited_block_names: ((link_state&.inherited_block_names || []) + block_names).sort.uniq,
inherited_dependencies: (link_state&.inherited_dependencies || {}).merge(dependencies || {}), ### merge, not replace, key data
- inherited_lines: code_merge(link_state&.inherited_lines, code_lines),
+ inherited_lines: HashDelegator.code_merge(link_state&.inherited_lines, code_lines),
next_block_name: link_block_data['block'] || '',
next_document_filename: next_document_filename,
next_load_file: next_document_filename == @delegate_object[:filename] ? LoadFile::Reuse : LoadFile::Load
)
end
end
- # Reads required code blocks from a temporary file specified by an environment variable.
- # @return [Array<String>] Lines read from the temporary file, or an empty array if file is not found or path is empty.
- def read_required_blocks_from_temp_file(temp_blocks_file_path)
- return [] if temp_blocks_file_path.to_s.empty?
-
- if File.exist?(temp_blocks_file_path)
- File.readlines(
- temp_blocks_file_path, chomp: true
- )
- else
- []
- end
- end
-
def runtime_exception(exception_sym, name, items)
if @delegate_object[exception_sym] != 0
data = { name: name, detail: items.join(', ') }
warn(
format(
@@ -1246,24 +1404,10 @@
return unless (@delegate_object[exception_sym]).positive?
exit @delegate_object[exception_sym]
end
- def safely_remove_file(path)
- FileUtils.rm_f(path)
- end
-
- # Evaluates the given string as Ruby code and rescues any StandardErrors.
- # If an error occurs, it calls the error_handler method with 'safeval'.
- # @param str [String] The string to be evaluated.
- # @return [Object] The result of evaluating the string.
- def safeval(str)
- eval(str)
- rescue StandardError
- error_handler('safeval')
- end
-
def save_to_file(required_lines)
write_command_file(required_lines)
@fout.fout "File saved: #{@run_state.saved_filespec}"
end
@@ -1273,57 +1417,57 @@
# Markdown document, obtain approval, and execute the chosen block of code.
#
# @return [Nil] Returns nil if no code block is selected or an error occurs.
def document_menu_loop
@menu_base_options = @delegate_object
- link_state, block_name_from_cli, now_using_cli = initialize_selection_states
+ link_state = LinkState.new(
+ block_name: @delegate_object[:block_name],
+ document_filename: @delegate_object[:filename]
+ )
+ block_name_from_cli = link_state.block_name.present?
+ @cli_block_name = link_state.block_name
+ now_using_cli = block_name_from_cli
menu_default_dname = nil
loop do
- # @bsp 'loop',block_name_from_cli,@cli_block_name
+ # &bsp 'loop', block_name_from_cli, @cli_block_name
block_name_from_cli, now_using_cli, blocks_in_file, menu_blocks, mdoc = \
set_delobj_menu_loop_vars(block_name_from_cli, now_using_cli, link_state)
# cli or user selection
#
block_state = load_cli_or_user_selected_block(blocks_in_file, menu_blocks,
menu_default_dname)
- if block_state.state == MenuState::EXIT
- # @bsp 'MenuState::EXIT -> break'
+ # &bsp 'block_name_from_cli:',block_name_from_cli
+ if !block_state
+ HashDelegator.error_handler('block_state missing', { abort: true })
+ elsif block_state.state == MenuState::EXIT
+ # &bsp 'load_cli_or_user_selected_block -> break'
break
end
dump_and_warn_block_state(block_state.block)
link_state, menu_default_dname = exec_bash_next_state(block_state.block, mdoc,
link_state)
if prompt_user_exit(block_name_from_cli, block_state.block)
- # @bsp 'prompt_user_exit -> break'
+ # &bsp 'prompt_user_exit -> break'
break
end
link_state.block_name, block_name_from_cli, cli_break = \
- next_state_from_cli(now_using_cli, block_state)
+ HashDelegator.next_link_state(!shift_cli_argument, now_using_cli, block_state)
- if cli_break
- # @bsp 'read_block_name_from_cli + next_link_state -> break'
+ if !block_state.block[:block_name_from_ui] && cli_break
+ # &bsp '!block_name_from_ui + cli_break -> break'
break
end
end
rescue StandardError
- error_handler('document_menu_loop',
- { abort: true })
+ HashDelegator.error_handler('document_menu_loop',
+ { abort: true })
end
- def next_state_from_cli(now_using_cli, block_state)
- was_using_cli = now_using_cli
- block_name_from_cli, = read_block_name_from_cli(now_using_cli)
- block_name, block_name_from_cli, cli_break = \
- next_link_state(block_name_from_cli, was_using_cli, block_state)
-
- [block_name, block_name_from_cli, cli_break]
- end
-
def exec_bash_next_state(block_state_block, mdoc, link_state)
lfls = execute_shell_type(
block_state_block,
mdoc,
link_state,
@@ -1340,11 +1484,11 @@
manage_cli_selection_state(block_name_from_cli, now_using_cli, link_state)
set_delob_filename_block_name(link_state, block_name_from_cli)
# update @delegate_object and @menu_base_options in auto_load
#
- blocks_in_file, menu_blocks, mdoc = mdoc_menu_and_blocks_from_nested_files
+ blocks_in_file, menu_blocks, mdoc = mdoc_menu_and_blocks_from_nested_files(link_state)
dump_delobj(blocks_in_file, menu_blocks, link_state)
[block_name_from_cli, now_using_cli, blocks_in_file, menu_blocks, mdoc]
end
@@ -1356,12 +1500,12 @@
@delegate_object[:pause_after_script_execution] &&
prompt_select_continue == MenuState::EXIT
end
def manage_cli_selection_state(block_name_from_cli, now_using_cli, link_state)
- if block_name_from_cli && @cli_block_name == '.'
- # @bsp 'pause cli control, allow user to select block'
+ if block_name_from_cli && @cli_block_name == @menu_base_options[:menu_persist_block_name]
+ # &bsp 'pause cli control, allow user to select block'
block_name_from_cli = false
now_using_cli = false
@menu_base_options[:block_name] = \
@delegate_object[:block_name] = \
link_state.block_name = \
@@ -1371,49 +1515,10 @@
@delegate_object = @menu_base_options.dup
@menu_user_clicked_back_link = false
[block_name_from_cli, now_using_cli]
end
- def next_link_state(block_name_from_cli, was_using_cli, block_state)
- # @bsp 'next_link_state',block_name_from_cli, was_using_cli, block_state
- # Set block_name based on block_name_from_cli
- block_name = block_name_from_cli ? @cli_block_name : nil
-
- # Determine the state of breaker based on was_using_cli and the block type
- breaker = !block_name_from_cli && was_using_cli && block_state.block[:shell] == BlockType::BASH
-
- # Reset block_name_from_cli if the conditions are not met
- block_name_from_cli ||= false
-
- [block_name, block_name_from_cli, breaker]
- end
-
- # Initialize the selection states for the execution loop.
- def initialize_selection_states
- link_state = LinkState.new(
- block_name: @delegate_object[:block_name],
- document_filename: @delegate_object[:filename]
- )
- block_name_from_cli, now_using_cli = handle_cli_block_name(link_state)
- [link_state, block_name_from_cli, now_using_cli]
- end
-
- # Update the state related to CLI block name.
- #
- # This method updates the flags indicating whether a CLI block name is being used
- # and if it was being used previously.
- #
- # @param block_name_from_cli [Boolean] Indicates if the block name is from CLI.
- # @return [Array] Returns the updated state of block name from CLI and its usage.
- def read_block_name_from_cli(block_name_from_cli)
- # was_using_cli = block_name_from_cli
- block_name_from_cli = shift_cli_argument!
- now_using_cli = block_name_from_cli
-
- [block_name_from_cli, now_using_cli]
- end
-
# Update the block name in the link state and delegate object.
#
# This method updates the block name based on whether it was specified
# through the CLI or derived from the link state.
#
@@ -1423,25 +1528,10 @@
@delegate_object[:filename] = link_state.document_filename
link_state.block_name = @delegate_object[:block_name] =
block_name_from_cli ? @cli_block_name : link_state.block_name
end
- # Handle CLI block name and determine the current CLI usage state.
- #
- # This method processes the CLI block name from the link state and sets
- # the initial state for CLI usage.
- #
- # @param link_state [LinkState] The current link state object.
- # @return [Array] Returns the state of block name from CLI and current usage of CLI.
- def handle_cli_block_name(link_state)
- block_name_from_cli = link_state.block_name.present?
- @cli_block_name = link_state.block_name
- now_using_cli = block_name_from_cli
-
- [block_name_from_cli, now_using_cli]
- end
-
# Outputs warnings based on the delegate object's configuration
#
# @param delegate_object [Hash] The delegate object containing configuration flags.
# @param blocks_in_file [Hash] Hash of blocks present in the file.
# @param menu_blocks [Hash] Hash of menu blocks.
@@ -1498,11 +1588,11 @@
end
)
rescue TTY::Reader::InputInterrupt
exit 1
rescue StandardError
- error_handler('select_option_with_metadata')
+ HashDelegator.error_handler('select_option_with_metadata')
end
def set_environment_variables_for_block(selected)
YAML.load(selected[:body].join("\n")).each do |key, value|
ENV[key] = value.to_s
@@ -1512,26 +1602,12 @@
{ key: key, value: value })
print string_send_color(formatted_string, :menu_vars_set_color)
end
end
- def set_environment_variables_per_array(vars)
- vars ||= []
- vars.each { |key, value| ENV[key] = value.to_s }
- end
-
- def set_file_permissions(file_path, chmod_value)
- File.chmod(chmod_value, file_path)
- end
-
- def set_script_block_name(selected)
- @run_state.script_block_name = selected[:oname]
- end
-
def should_add_back_option?
@delegate_object[:menu_with_back] && @link_history.prior_state_exist?
- # @delegate_object[:menu_with_back] && link_history_prior_state_exist?
end
# Initializes a new fenced code block (FCB) object based on the provided line and heading information.
# @param line [String] The line initiating the fenced block.
# @param headings [Array<String>] Current headings hierarchy.
@@ -1571,50 +1647,14 @@
# The color method is fetched from @delegate_object and applied to the string.
# @param string [String] The string to which the color will be applied.
# @param color_sym [Symbol] The symbol representing the color method.
# @param default [String] Default color method to use if color_sym is not found in @delegate_object.
# @return [String] The string with the applied color method.
- def string_send_color(string, color_sym, default: 'plain')
- color_method = @delegate_object.fetch(color_sym, default).to_sym
- string.to_s.send(color_method)
+ def string_send_color(string, color_sym)
+ HashDelegator.apply_color_from_hash(string, @delegate_object, color_sym)
end
- # Creates a TTY prompt with custom settings. Specifically, it disables the default 'cross' symbol and
- # defines a lambda function to handle interrupts.
- # @return [TTY::Prompt] A new TTY::Prompt instance with specified configurations.
- def tty_prompt_without_disabled_symbol
- TTY::Prompt.new(
- interrupt: lambda {
- puts
- raise TTY::Reader::InputInterrupt
- },
- symbols: { cross: ' ' }
- )
- end
-
- # Updates the hierarchy of document headings based on the given line.
- # Utilizes regular expressions to identify heading levels.
- # @param line [String] The line of text to check for headings.
- # @param headings [Array<String>] Current headings hierarchy.
- # @return [Array<String>] Updated headings hierarchy.
- def update_document_headings(line, headings)
- heading3_match = Regexp.new(@delegate_object[:heading3_match])
- heading2_match = Regexp.new(@delegate_object[:heading2_match])
- heading1_match = Regexp.new(@delegate_object[:heading1_match])
-
- case line
- when heading3_match
- [headings[0], headings[1], $~[:name]]
- when heading2_match
- [headings[0], $~[:name]]
- when heading1_match
- [$~[:name]]
- else
- headings
- end
- end
-
##
# Processes an individual line within a loop, updating headings and handling fenced code blocks.
# This function is designed to be called within a loop that iterates through each line of a document.
#
# @param line [String] The current line being processed.
@@ -1633,20 +1673,16 @@
# @return [Void] The function modifies the `state` and `selected_messages` arguments in place.
##
def update_line_and_block_state(nested_line, state, selected_messages,
&block)
line = nested_line.to_s
- if @delegate_object[:menu_blocks_with_headings]
- state[:headings] = update_document_headings(line, state[:headings])
- end
-
if line.match(@delegate_object[:fenced_start_and_end_regex])
if state[:in_fenced_block]
## end of code block
#
- update_menu_attrib_yield_selected(state[:fcb], selected_messages,
- &block)
+ HashDelegator.update_menu_attrib_yield_selected(state[:fcb], selected_messages, @delegate_object,
+ &block)
state[:in_fenced_block] = false
else
## start of code block
#
state[:fcb] =
@@ -1663,33 +1699,18 @@
line.chomp.sub(/^#{state[:fcb].indent}/, '')
]
elsif nested_line[:depth].zero? || @delegate_object[:menu_include_imported_notes]
# add line if it is depth 0 or option allows it
#
- yield_line_if_selected(line, selected_messages, &block)
+ HashDelegator.yield_line_if_selected(line, selected_messages, &block)
else
- # @bsp 'line is not recognized for block state'
+ # &bsp 'line is not recognized for block state'
end
end
- # Updates the attributes of the given fcb object and conditionally yields to a block.
- # It initializes fcb names and sets the default block title from fcb's body.
- # If the fcb has a body and meets certain conditions, it yields to the given block.
- #
- # @param fcb [Object] The fcb object whose attributes are to be updated.
- # @param selected_messages [Array<Symbol>] A list of message types to determine if yielding is applicable.
- # @param block [Block] An optional block to yield to if conditions are met.
- def update_menu_attrib_yield_selected(fcb, selected_messages, &block)
- initialize_fcb_names(fcb)
- return unless fcb.body
-
- default_block_title_from_body(fcb)
- yield_to_block_if_applicable(fcb, selected_messages, &block)
- end
-
# Processes YAML data from the selected menu item, updating delegate objects and optionally printing formatted output.
# @param selected [Hash] Selected item from the menu containing a YAML body.
# @param tgt2 [Hash, nil] An optional target hash to update with YAML data.
# @return [LoadFileLinkState] An instance indicating the next action for loading files.
def read_show_options_and_trigger_reuse(selected, link_state = LinkState.new)
@@ -1715,14 +1736,14 @@
end
end
def wait_for_user_selected_block(all_blocks, menu_blocks, default)
block_state = wait_for_user_selection(all_blocks, menu_blocks, default)
- handle_block_state(block_state)
+ handle_back_or_continue(block_state)
block_state
rescue StandardError
- error_handler('wait_for_user_selected_block')
+ HashDelegator.error_handler('wait_for_user_selected_block')
end
def wait_for_user_selection(_all_blocks, menu_blocks, default)
prompt_title = string_send_color(
@delegate_object[:prompt_select_block].to_s, :prompt_color_after_script_execution
@@ -1769,38 +1790,19 @@
"# file_name: #{@delegate_object[:filename]}\n" \
"# block_name: #{@delegate_object[:block_name]}\n" \
"# time: #{time_now}\n" \
"#{required_lines.flatten.join("\n")}\n"
- create_file_and_write_string_with_permissions(
+ HashDelegator.create_file_and_write_string_with_permissions(
@run_state.saved_filespec,
content,
@delegate_object[:saved_script_chmod]
)
rescue StandardError
- error_handler('write_command_file')
+ HashDelegator.error_handler('write_command_file')
end
- def save_executed_script_if_specified(lines)
- write_command_file(lines) if @delegate_object[:save_executed_script]
- end
-
- def write_execution_output_to_file
- FileUtils.mkdir_p File.dirname(@delegate_object[:logged_stdout_filespec])
-
- File.write(
- @delegate_object[:logged_stdout_filespec],
- ["-STDOUT-\n",
- format_execution_streams(ExecutionStreams::StdOut),
- "-STDERR-\n",
- format_execution_streams(ExecutionStreams::StdErr),
- "-STDIN-\n",
- format_execution_streams(ExecutionStreams::StdIn),
- "\n"].join
- )
- end
-
# Writes required code blocks to a temporary file and sets an environment variable with its path.
#
# @param mdoc [Object] The Markdown document object.
# @param block_name [String] The name of the block to collect code for.
def write_required_blocks_to_file(mdoc, block_name, temp_file_path, import_filename: nil)
@@ -1812,45 +1814,15 @@
)[:code]
else
[]
end
- code_blocks = (read_required_blocks_from_temp_file(import_filename) +
+ code_blocks = (HashDelegator.read_required_blocks_from_temp_file(import_filename) +
c1).join("\n")
- write_code_to_file(code_blocks, temp_file_path)
+ HashDelegator.write_code_to_file(code_blocks, temp_file_path)
end
-
- # Writes the provided code blocks to a file.
- # @param code_blocks [String] Code blocks to write into the file.
- def write_code_to_file(content, path)
- File.write(path, content)
- end
-
- # Yields a line as a new block if the selected message type includes :line.
- # @param [String] line The line to be processed.
- # @param [Array<Symbol>] selected_messages A list of message types to check.
- # @param [Proc] block The block to be called with the line data.
- def yield_line_if_selected(line, selected_messages, &block)
- return unless block && selected_messages.include?(:line)
-
- block.call(:line, FCB.new(body: [line]))
- end
-
- # Yields to the provided block with specified parameters if certain conditions are met.
- # The method checks if a block is given, if the selected_messages include :blocks,
- # and if the fcb_select? method from MarkdownExec::Filter returns true for the given fcb.
- #
- # @param fcb [Object] The object to be evaluated and potentially passed to the block.
- # @param selected_messages [Array<Symbol>] A collection of message types, one of which must be :blocks.
- # @param block [Block] A block to be called if conditions are met.
- def yield_to_block_if_applicable(fcb, selected_messages, &block)
- if block_given? && selected_messages.include?(:blocks) &&
- MarkdownExec::Filter.fcb_select?(@delegate_object, fcb)
- block.call :blocks, fcb
- end
- end
end
end
if $PROGRAM_NAME == __FILE__
require 'bundler/setup'
@@ -1915,34 +1887,34 @@
def test_indent_all_lines_with_indent
body = "Line 1\nLine 2"
indent = ' ' # Two spaces
expected_result = " Line 1\n Line 2"
- assert_equal expected_result, @hd.indent_all_lines(body, indent)
+ assert_equal expected_result, HashDelegator.indent_all_lines(body, indent)
end
def test_indent_all_lines_without_indent
body = "Line 1\nLine 2"
indent = nil
- assert_equal body, @hd.indent_all_lines(body, indent)
+ assert_equal body, HashDelegator.indent_all_lines(body, indent)
end
def test_indent_all_lines_with_empty_indent
body = "Line 1\nLine 2"
indent = ''
- assert_equal body, @hd.indent_all_lines(body, indent)
+ assert_equal body, HashDelegator.indent_all_lines(body, indent)
end
def test_safeval_successful_evaluation
- assert_equal 4, @hd.safeval('2 + 2')
+ assert_equal 4, HashDelegator.safeval('2 + 2')
end
def test_safeval_rescue_from_error
- @hd.stubs(:error_handler).with('safeval')
- assert_nil @hd.safeval('invalid code')
+ HashDelegator.stubs(:error_handler).with('safeval')
+ assert_nil HashDelegator.safeval('invalid code')
end
def test_set_fcb_title
# sample input and output data for testing default_block_title_from_body method
input_output_data = [
@@ -1966,11 +1938,11 @@
# iterate over the input and output data and
# assert that the method sets the title as expected
input_output_data.each do |data|
input = data[:input]
output = data[:output]
- @hd.default_block_title_from_body(input)
+ HashDelegator.default_block_title_from_body(input)
assert_equal output, input.title
end
end
class TestHashDelegatorAppendDivider < Minitest::Test
@@ -1981,11 +1953,11 @@
menu_initial_divider: 'Initial Divider',
menu_final_divider: 'Final Divider',
menu_divider_color: :color
})
@hd.stubs(:string_send_color).returns('Formatted Divider')
- @hd.stubs(:safeval).returns('Safe Value')
+ HashDelegator.stubs(:safeval).returns('Safe Value')
end
def test_append_divider_initial
menu_blocks = []
@hd.append_divider(menu_blocks, :initial)
@@ -2016,23 +1988,23 @@
@hd = HashDelegator.new
end
def test_block_find_with_match
blocks = [{ key: 'value1' }, { key: 'value2' }]
- result = @hd.block_find(blocks, :key, 'value1')
+ result = HashDelegator.block_find(blocks, :key, 'value1')
assert_equal({ key: 'value1' }, result)
end
def test_block_find_without_match
blocks = [{ key: 'value1' }, { key: 'value2' }]
- result = @hd.block_find(blocks, :key, 'value3')
+ result = HashDelegator.block_find(blocks, :key, 'value3')
assert_nil result
end
def test_block_find_with_default
blocks = [{ key: 'value1' }, { key: 'value2' }]
- result = @hd.block_find(blocks, :key, 'value3', 'default')
+ result = HashDelegator.block_find(blocks, :key, 'value3', 'default')
assert_equal 'default', result
end
end
class TestHashDelegatorBlocksFromNestedFiles < Minitest::Test
@@ -2040,11 +2012,11 @@
@hd = HashDelegator.new
@hd.stubs(:iter_blocks_from_nested_files).yields(:blocks, FCB.new)
@hd.stubs(:get_block_summary).returns(FCB.new)
@hd.stubs(:create_and_add_chrome_blocks)
@hd.instance_variable_set(:@delegate_object, {})
- @hd.stubs(:error_handler)
+ HashDelegator.stubs(:error_handler)
end
def test_blocks_from_nested_files
result = @hd.blocks_from_nested_files
@@ -2066,11 +2038,11 @@
def setup
@hd = HashDelegator.new
@hd.instance_variable_set(:@delegate_object, {})
@mdoc = mock('YourMDocClass')
@selected = { shell: BlockType::VARS, body: ['key: value'] }
- @hd.stubs(:read_required_blocks_from_temp_file).returns([])
+ HashDelegator.stubs(:read_required_blocks_from_temp_file).returns([])
@hd.stubs(:string_send_color)
@hd.stubs(:print)
end
def test_collect_required_code_lines_with_vars
@@ -2084,33 +2056,33 @@
class TestHashDelegatorCommandOrUserSelectedBlock < Minitest::Test
def setup
@hd = HashDelegator.new
@hd.instance_variable_set(:@delegate_object, {})
- @hd.stubs(:error_handler)
+ HashDelegator.stubs(:error_handler)
@hd.stubs(:wait_for_user_selected_block)
end
def test_command_selected_block
all_blocks = [{ oname: 'block1' }, { oname: 'block2' }]
@hd.instance_variable_set(:@delegate_object,
{ block_name: 'block1' })
result = @hd.load_cli_or_user_selected_block(all_blocks, [], nil)
- assert_equal all_blocks.first, result.block
+ assert_equal all_blocks.first.merge(block_name_from_ui: false), result.block
assert_nil result.state
end
def test_user_selected_block
block_state = SelectedBlockMenuState.new({ oname: 'block2' },
:some_state)
@hd.stubs(:wait_for_user_selected_block).returns(block_state)
result = @hd.load_cli_or_user_selected_block([], [], nil)
- assert_equal block_state.block, result.block
+ assert_equal block_state.block.merge(block_name_from_ui: true), result.block
assert_equal :some_state, result.state
end
end
class TestHashDelegatorCountBlockInFilename < Minitest::Test
@@ -2143,11 +2115,11 @@
end
class TestHashDelegatorCreateAndWriteFile < Minitest::Test
def setup
@hd = HashDelegator.new
- @hd.stubs(:error_handler)
+ HashDelegator.stubs(:error_handler)
FileUtils.stubs(:mkdir_p)
File.stubs(:write)
File.stubs(:chmod)
end
@@ -2158,12 +2130,12 @@
FileUtils.expects(:mkdir_p).with('/path/to').once
File.expects(:write).with(file_path, content).once
File.expects(:chmod).with(chmod_value, file_path).once
- @hd.create_file_and_write_string_with_permissions(file_path, content,
- chmod_value)
+ HashDelegator.create_file_and_write_string_with_permissions(file_path, content,
+ chmod_value)
assert true # Placeholder for actual test assertions
end
def test_create_and_write_file_without_chmod
@@ -2173,12 +2145,12 @@
FileUtils.expects(:mkdir_p).with('/path/to').once
File.expects(:write).with(file_path, content).once
File.expects(:chmod).never
- @hd.create_file_and_write_string_with_permissions(file_path, content,
- chmod_value)
+ HashDelegator.create_file_and_write_string_with_permissions(file_path, content,
+ chmod_value)
assert true # Placeholder for actual test assertions
end
end
@@ -2305,31 +2277,28 @@
@hd = HashDelegator.new
@hd.instance_variable_set(:@run_state, mock('run_state'))
end
def test_format_execution_streams_with_valid_key
- @hd.instance_variable_get(:@run_state).stubs(:files).returns({ stdout: %w[
- output1 output2
- ] })
+ result = HashDelegator.format_execution_streams(:stdout,
+ { stdout: %w[output1 output2] })
- result = @hd.format_execution_streams(:stdout)
-
assert_equal 'output1output2', result
end
def test_format_execution_streams_with_empty_key
@hd.instance_variable_get(:@run_state).stubs(:files).returns({})
- result = @hd.format_execution_streams(:stderr)
+ result = HashDelegator.format_execution_streams(:stderr)
assert_equal '', result
end
def test_format_execution_streams_with_nil_files
@hd.instance_variable_get(:@run_state).stubs(:files).returns(nil)
- result = @hd.format_execution_streams(:stdin)
+ result = HashDelegator.format_execution_streams(:stdin)
assert_equal '', result
end
end
@@ -2356,37 +2325,37 @@
def setup
@hd = HashDelegator.new
@mock_block_state = mock('block_state')
end
- def test_handle_block_state_with_back
+ def test_handle_back_or_continue_with_back
@mock_block_state.stubs(:state).returns(MenuState::BACK)
@mock_block_state.stubs(:block).returns({ oname: 'sample_block' })
- @hd.handle_block_state(@mock_block_state)
+ @hd.handle_back_or_continue(@mock_block_state)
assert_equal 'sample_block',
@hd.instance_variable_get(:@delegate_object)[:block_name]
assert @hd.instance_variable_get(:@menu_user_clicked_back_link)
end
- def test_handle_block_state_with_continue
+ def test_handle_back_or_continue_with_continue
@mock_block_state.stubs(:state).returns(MenuState::CONTINUE)
@mock_block_state.stubs(:block).returns({ oname: 'another_block' })
- @hd.handle_block_state(@mock_block_state)
+ @hd.handle_back_or_continue(@mock_block_state)
assert_equal 'another_block',
@hd.instance_variable_get(:@delegate_object)[:block_name]
refute @hd.instance_variable_get(:@menu_user_clicked_back_link)
end
- def test_handle_block_state_with_other
+ def test_handle_back_or_continue_with_other
@mock_block_state.stubs(:state).returns(nil) # MenuState::OTHER
@mock_block_state.stubs(:block).returns({ oname: 'other_block' })
- @hd.handle_block_state(@mock_block_state)
+ @hd.handle_back_or_continue(@mock_block_state)
assert_nil @hd.instance_variable_get(:@delegate_object)[:block_name]
assert_nil @hd.instance_variable_get(:@menu_user_clicked_back_link)
end
end
@@ -2527,11 +2496,11 @@
@hd = HashDelegator.new
@hd.instance_variable_set(:@delegate_object, {
menu_option_back_name: "'Back'",
menu_chrome_format: '-- %s --'
})
- @hd.stubs(:safeval).with("'Back'").returns('Back')
+ HashDelegator.stubs(:safeval).with("'Back'").returns('Back')
end
def test_menu_chrome_formatted_option_with_format
assert_equal '-- Back --',
@hd.menu_chrome_formatted_option(:menu_option_back_name)
@@ -2582,84 +2551,61 @@
end
end
def test_yield_line_if_selected_with_line
block_called = false
- @hd.yield_line_if_selected('Test line', [:line]) do |type, content|
+ HashDelegator.yield_line_if_selected('Test line', [:line]) do |type, content|
block_called = true
assert_equal :line, type
assert_equal 'Test line', content.body[0]
end
assert block_called
end
def test_yield_line_if_selected_without_line
block_called = false
- @hd.yield_line_if_selected('Test line', [:other]) do |_|
+ HashDelegator.yield_line_if_selected('Test line', [:other]) do |_|
block_called = true
end
refute block_called
end
def test_yield_line_if_selected_without_block
- result = @hd.yield_line_if_selected('Test line', [:line])
+ result = HashDelegator.yield_line_if_selected('Test line', [:line])
assert_nil result
end
end
- class TestHashDelegator < Minitest::Test
- def setup
- @hd = HashDelegator.new
- @hd.instance_variable_set(:@delegate_object, {
- heading1_match: '^# (?<name>.+)$',
- heading2_match: '^## (?<name>.+)$',
- heading3_match: '^### (?<name>.+)$'
- })
- end
-
- def test_update_document_headings
- assert_equal(['Heading 1'],
- @hd.update_document_headings('# Heading 1', []))
- assert_equal(['Heading 1', 'Heading 2'],
- @hd.update_document_headings('## Heading 2',
- ['Heading 1']))
- assert_equal(['Heading 1', 'Heading 2', 'Heading 3'],
- @hd.update_document_headings('### Heading 3',
- ['Heading 1', 'Heading 2']))
- assert_equal([], @hd.update_document_headings('Regular text', []))
- end
- end
-
class TestHashDelegatorUpdateMenuAttribYieldSelectedWithBody < Minitest::Test
def setup
@hd = HashDelegator.new
@fcb = mock('Fcb')
@fcb.stubs(:body).returns(true)
- @hd.stubs(:initialize_fcb_names)
- @hd.stubs(:default_block_title_from_body)
- @hd.stubs(:yield_to_block_if_applicable)
+ HashDelegator.stubs(:initialize_fcb_names)
+ HashDelegator.stubs(:default_block_title_from_body)
+ Filter.stubs(:yield_to_block_if_applicable)
end
def test_update_menu_attrib_yield_selected_with_body
- @hd.expects(:initialize_fcb_names).with(@fcb)
- @hd.expects(:default_block_title_from_body).with(@fcb)
- @hd.expects(:yield_to_block_if_applicable).with(@fcb, [:some_message])
+ HashDelegator.expects(:initialize_fcb_names).with(@fcb)
+ HashDelegator.expects(:default_block_title_from_body).with(@fcb)
+ Filter.expects(:yield_to_block_if_applicable).with(@fcb, [:some_message], {})
- @hd.update_menu_attrib_yield_selected(@fcb, [:some_message])
+ HashDelegator.update_menu_attrib_yield_selected(@fcb, [:some_message])
end
def test_update_menu_attrib_yield_selected_without_body
@fcb.stubs(:body).returns(nil)
- @hd.expects(:initialize_fcb_names).with(@fcb)
- @hd.update_menu_attrib_yield_selected(@fcb, [:some_message])
+ HashDelegator.expects(:initialize_fcb_names).with(@fcb)
+ HashDelegator.update_menu_attrib_yield_selected(@fcb, [:some_message])
end
end
class TestHashDelegatorWaitForUserSelectedBlock < Minitest::Test
def setup
@hd = HashDelegator.new
- @hd.stubs(:error_handler)
+ HashDelegator.stubs(:error_handler)
end
def test_wait_for_user_selected_block_with_back_state
mock_block_state = Struct.new(:state, :block).new(MenuState::BACK,
{ oname: 'back_block' })
@@ -2697,26 +2643,26 @@
MarkdownExec::Filter.stubs(:fcb_select?).returns(true)
end
def test_yield_to_block_if_applicable_with_correct_conditions
block_called = false
- @hd.yield_to_block_if_applicable(@fcb, [:blocks]) do |type, fcb|
+ Filter.yield_to_block_if_applicable(@fcb, [:blocks]) do |type, fcb|
block_called = true
assert_equal :blocks, type
assert_equal @fcb, fcb
end
assert block_called
end
def test_yield_to_block_if_applicable_without_block
- result = @hd.yield_to_block_if_applicable(@fcb, [:blocks])
+ result = Filter.yield_to_block_if_applicable(@fcb, [:blocks])
assert_nil result
end
def test_yield_to_block_if_applicable_with_incorrect_conditions
block_called = false
MarkdownExec::Filter.stubs(:fcb_select?).returns(false)
- @hd.yield_to_block_if_applicable(@fcb, [:non_blocks]) do |_|
+ Filter.yield_to_block_if_applicable(@fcb, [:non_blocks]) do |_|
block_called = true
end
refute block_called
end
end