lib/hash_delegator.rb in markdown_exec-2.3.0 vs lib/hash_delegator.rb in markdown_exec-2.4.0
- old
+ new
@@ -15,33 +15,37 @@
require 'tempfile'
require 'tmpdir'
require 'tty-prompt'
require 'yaml'
+require_relative 'ansi_string'
require_relative 'array'
require_relative 'array_util'
require_relative 'block_label'
require_relative 'block_types'
require_relative 'cached_nested_file_reader'
require_relative 'constants'
require_relative 'directory_searcher'
require_relative 'exceptions'
require_relative 'fcb'
require_relative 'filter'
+require_relative 'format_table'
require_relative 'fout'
require_relative 'hash'
require_relative 'hierarchy_string'
require_relative 'link_history'
require_relative 'mdoc'
require_relative 'namer'
require_relative 'regexp'
require_relative 'resize_terminal'
-require_relative 'std_out_err_logger'
require_relative 'streams_out'
require_relative 'string_util'
+require_relative 'table_extractor'
require_relative 'text_analyzer'
+require_relative 'argument_processor'
+
$pd = false unless defined?($pd)
class String
# Checks if the string is not empty.
# @return [Boolean] Returns true if the string is not empty, false otherwise.
@@ -50,21 +54,24 @@
end
end
module HashDelegatorSelf
# 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.
+ # 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'.
+ # @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)
+ AnsiString.new(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.
@@ -82,19 +89,25 @@
# }
# 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 message sent to an element matches a given value.
- # This method is particularly useful for finding a specific hash-like object within an enumerable collection.
+ # Searches for the first element in a collection where the specified
+ # message sent to an element 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 msg [Symbol, String] The message to send to each element of the collection.
- # @param value [Object] The value to match against the result of the message sent to each element.
- # @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.
+ # @param msg [Symbol, String] The message to send to each element of
+ # the collection.
+ # @param value [Object] The value to match against the result of the message
+ # sent to each element.
+ # @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, msg, value, default = nil)
blocks.find { |item| item.send(msg) == value } || default
end
def code_merge(*bodies)
@@ -108,15 +121,17 @@
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.
+ # 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.
+ # @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?
@@ -126,11 +141,12 @@
# 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.
+ # 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
@@ -172,11 +188,12 @@
# end.join("\n")
# 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).
+ # @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")
@@ -269,17 +286,55 @@
def set_file_permissions(file_path, chmod_value)
File.chmod(chmod_value, file_path)
end
+ # find tables in multiple lines and format horizontally
+ def tables_into_columns!(blocks_menu, delegate_object)
+ return unless delegate_object[:tables_into_columns]
+
+ lines = blocks_menu.map(&:oname)
+ text_tables = TableExtractor.extract_tables(lines)
+ return unless text_tables.count.positive?
+
+ text_tables.each do |match|
+ range = match[:start_index]..(match[:start_index] + match[:rows] - 1)
+ lines = blocks_menu[range].map(&:oname)
+ formatted = MarkdownTableFormatter.format_table(
+ lines,
+ match[:columns],
+ decorate: {
+ border: delegate_object[:table_border_color],
+ header_row: delegate_object[:table_header_row_color],
+ row: delegate_object[:table_row_color],
+ separator_line: delegate_object[:table_separator_line_color]
+ }
+ )
+
+ if formatted.count == range.size
+ # read indentation from first line
+ indent = blocks_menu[range.first].oname.split('|', 2).first
+
+ # replace text in each block
+ range.each.with_index do |block_ind, ind|
+ ### format oname to dname
+ blocks_menu[block_ind].dname = indent + formatted[ind]
+ end
+ else
+ warn [__LINE__, range, lines, formatted].inspect
+ raise 'Invalid result from MarkdownTableFormatter.format_table()'
+ end
+ end
+ 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
+ puts # next line in case not at start
raise TTY::Reader::InputInterrupt
},
symbols: { cross: ' ' }
)
end
@@ -287,11 +342,11 @@
# 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 selected_types [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:, messages:, configuration: {},
&block)
initialize_fcb_names(fcb)
return unless fcb.body
@@ -301,14 +356,14 @@
&block)
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 [Array<Symbol>] selected_types 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)
+ def yield_line_if_selected(line, selected_types, &block)
+ return unless block && block_type_selected?(selected_types, :line)
block.call(:line, MarkdownExec::FCB.new(body: [line]))
end
end
@@ -478,11 +533,12 @@
@@printed_messages.add(str)
end
end
class HashDelegatorParent
- attr_accessor :most_recent_loaded_filename, :pass_args, :run_state
+ attr_accessor :most_recent_loaded_filename, :pass_args, :run_state,
+ :p_all_arguments, :p_options_parsed, :p_params, :p_rest
extend HashDelegatorSelf
include CompactionHelpers
include TextAnalyzer
@@ -499,10 +555,15 @@
@link_history = LinkHistory.new
@fout = FOut.new(@delegate_object) ### slice only relevant keys
@process_mutex = Mutex.new
@process_cv = ConditionVariable.new
+
+ @p_all_arguments = []
+ @p_options_parsed = []
+ @p_params = {}
+ @p_rest = []
end
# private
# def [](key)
@@ -511,10 +572,30 @@
# def []=(key, value)
# @delegate_object[key] = value
# end
+ ##
+ # Returns the absolute path of the given file path.
+ # If the provided path is already absolute, it returns it as is.
+ # Otherwise, it prefixes the path with the current working directory.
+ #
+ # @param file_path [String] The file path to process
+ # @return [String] The absolute path
+ #
+ # Example usage:
+ # absolute_path('/absolute/path/to/file.txt') # => '/absolute/path/to/file.txt'
+ # absolute_path('relative/path/to/file.txt') # => '/current/working/directory/relative/path/to/file.txt'
+ #
+ def absolute_path(file_path)
+ if File.absolute_path?(file_path)
+ file_path
+ else
+ File.join(Dir.getwd, file_path)
+ end
+ end
+
# 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:, link_state:)
@@ -531,24 +612,20 @@
# exit after other options
if @delegate_object[:menu_with_exit]
add_exit_option(menu_blocks: menu_blocks)
end
- add_dividers(menu_blocks: menu_blocks)
+ append_divider(menu_blocks: menu_blocks, position: :initial)
+ append_divider(menu_blocks: menu_blocks, position: :final)
end
private
def add_back_option(menu_blocks:)
append_chrome_block(menu_blocks: menu_blocks, menu_state: MenuState::BACK)
end
- def add_dividers(menu_blocks:)
- append_divider(menu_blocks: menu_blocks, position: :initial)
- append_divider(menu_blocks: menu_blocks, position: :final)
- end
-
def add_exit_option(menu_blocks:)
append_chrome_block(menu_blocks: menu_blocks, menu_state: MenuState::EXIT)
end
def add_inherited_lines(menu_blocks:, link_state:)
@@ -597,10 +674,11 @@
chrome_block = FCB.new(
chrome: true,
dname: HashDelegator.new(@delegate_object).string_send_color(
formatted_name, :menu_chrome_color
),
+ nickname: formatted_name,
oname: formatted_name
)
if insert_at_top
menu_blocks.unshift(chrome_block)
@@ -726,10 +804,13 @@
def blocks_find_by_block_name(blocks, block_name)
@dml_blocks_in_file.find do |item|
# 2024-08-04 match oname for long block names
# 2024-08-04 match nickname for long block names
block_name == item.pub_name || block_name == item.nickname || block_name == item.oname
+ end || @dml_menu_blocks.find do |item|
+ # 2024-08-22 search in menu blocks to allow matching of automatic chrome with nickname
+ block_name == item.pub_name || block_name == item.nickname || block_name == item.oname
end
end
# private
@@ -812,11 +893,11 @@
warn format_and_highlight_dependencies(dependencies,
highlight: required[:unmet_dependencies])
runtime_exception(:runtime_exception_error_level,
'unmet_dependencies, flag: runtime_exception_error_level',
required[:unmet_dependencies])
- elsif false ### use option 2024-08-02
+ elsif @delegate_object[:dump_dependencies]
warn format_and_highlight_dependencies(dependencies,
highlight: [@delegate_object[:block_name]])
end
if selected[:shell] == BlockType::OPTS
@@ -896,31 +977,34 @@
#
# @param mdoc [Object] The markdown document object containing code blocks.
# @param selected [Hash] The selected item from the menu to be executed.
# @return [LoadFileLinkState] An object indicating whether to load the next block or reuse the current one.
def compile_execute_and_trigger_reuse(mdoc:, selected:, block_source:,
- link_state: nil)
- required_lines = collect_required_code_lines(mdoc: mdoc, selected: selected, link_state: link_state,
- block_source: block_source)
+ link_state:)
+ required_lines = collect_required_code_lines(
+ mdoc: mdoc, selected: selected,
+ link_state: link_state, block_source: block_source
+ )
output_or_approval = @delegate_object[:output_script] || @delegate_object[:user_must_approve]
if output_or_approval
display_required_code(required_lines: required_lines)
end
allow_execution = if @delegate_object[:user_must_approve]
- prompt_for_user_approval(required_lines: required_lines,
- selected: selected)
+ prompt_for_user_approval(
+ required_lines: required_lines,
+ selected: selected
+ )
else
true
end
if allow_execution
execute_required_lines(required_lines: required_lines,
selected: selected)
end
link_state.block_name = nil
- LoadFileLinkState.new(LoadFile::REUSE, link_state)
end
# Check if the expression contains wildcard characters
def contains_wildcards?(expr)
expr.match(%r{\*|\?|\[})
@@ -1134,352 +1218,43 @@
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 do_save_execution_output
- return unless @delegate_object[:save_execution_output]
- return if @run_state.in_own_window
+ def dml_menu_append_chrome_item(
+ name, count, type, menu_state: MenuState::LOAD,
+ always_create: true, always_enable: true
+ )
+ raise unless name.present?
+ raise if @dml_menu_blocks.nil?
- @run_state.files.write_execution_output_to_file(@delegate_object[:logged_stdout_filespec])
- end
+ item = @dml_menu_blocks.find { |block| block.oname == name }
- # Select and execute a code block from a Markdown document.
- #
- # This method allows the user to interactively select a code block from a
- # 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_inpseq
- @menu_base_options = @delegate_object
- @dml_link_state = LinkState.new(
- block_name: @delegate_object[:block_name],
- document_filename: @delegate_object[:filename]
- )
- @run_state.source.block_name_from_cli = @dml_link_state.block_name.present?
- @cli_block_name = @dml_link_state.block_name
- @dml_now_using_cli = @run_state.source.block_name_from_cli
- @dml_menu_default_dname = nil
- @dml_block_state = SelectedBlockMenuState.new
- @doc_saved_lines_files = []
+ # create menu item when it is needed (count > 0)
+ #
+ if item.nil? && (always_create || count.positive?)
+ item = append_chrome_block(menu_blocks: @dml_menu_blocks,
+ menu_state: menu_state)
+ end
- ## load file with code lines per options
+ # update item if it exists
#
- if @menu_base_options[:load_code].present?
- @dml_link_state.inherited_lines =
- @menu_base_options[:load_code].split(':').map do |path|
- File.readlines(path, chomp: true)
- end.flatten(1)
+ return unless item
- inherited_block_names = []
- inherited_dependencies = {}
- selected = FCB.new(oname: 'load_code')
- pop_add_current_code_to_head_and_trigger_load(@dml_link_state, inherited_block_names,
- code_lines, inherited_dependencies, selected)
+ item.dname = type.present? ? "#{name} (#{count} #{type})" : name
+ if always_enable || count.positive?
+ item.delete(:disabled)
+ else
+ item[:disabled] = ''
end
+ end
- fdo = ->(option) {
- name = format(@delegate_object[:menu_link_format],
- HashDelegator.safeval(@delegate_object[option]))
- OpenStruct.new(
- dname: name,
- oname: name,
- name: name,
- pub_name: name.pub_name
- )
- }
- item_back = fdo.call(:menu_option_back_name)
- item_edit = fdo.call(:menu_option_edit_name)
- item_history = fdo.call(:menu_option_history_name)
- item_load = fdo.call(:menu_option_load_name)
- item_save = fdo.call(:menu_option_save_name)
- item_shell = fdo.call(:menu_option_shell_name)
- item_view = fdo.call(:menu_option_view_name)
+ def do_save_execution_output
+ return unless @delegate_object[:save_execution_output]
+ return if @run_state.in_own_window
- @run_state.batch_random = Random.new.rand
- @run_state.batch_index = 0
-
- @run_state.files = StreamsOut.new
-
- InputSequencer.new(
- @delegate_object[:filename],
- @delegate_object[:input_cli_rest]
- ).run do |msg, data|
- # &bt msg
- case msg
- when :parse_document # once for each menu
- # puts "@ - parse document #{data}"
- inpseq_parse_document(data)
-
- if @delegate_object[:menu_for_history]
- history_files(@dml_link_state).tap do |files|
- if files.count.positive?
- menu_enable_option(item_history.oname, files.count, 'files',
- menu_state: MenuState::HISTORY)
- end
- end
- end
-
- if @delegate_object[:menu_for_saved_lines] && @delegate_object[:document_saved_lines_glob].present?
-
- sf = document_name_in_glob_as_file_name(
- @dml_link_state.document_filename,
- @delegate_object[:document_saved_lines_glob]
- )
- files = sf ? Dir.glob(sf) : []
- @doc_saved_lines_files = files.count.positive? ? files : []
-
- lines_count = @dml_link_state.inherited_lines_count
-
- # add menu items (glob, load, save) and enable selectively
- if files.count.positive? || lines_count.positive?
- menu_add_disabled_option(sf)
- end
- if files.count.positive?
- menu_enable_option(item_load.dname, files.count, 'files',
- menu_state: MenuState::LOAD)
- end
- if lines_count.positive?
- menu_enable_option(item_edit.dname, lines_count, 'lines',
- menu_state: MenuState::EDIT)
- end
- if lines_count.positive?
- menu_enable_option(item_save.dname, 1, '',
- menu_state: MenuState::SAVE)
- end
- if lines_count.positive?
- menu_enable_option(item_view.dname, 1, '',
- menu_state: MenuState::VIEW)
- end
- if @delegate_object[:menu_with_shell]
- menu_enable_option(item_shell.dname, 1, '',
- menu_state: MenuState::SHELL)
- end
-
- # # reflect new menu items
- # @dml_mdoc = MDoc.new(@dml_menu_blocks)
- end
-
- when :display_menu
- # warn "@ - display menu:"
- # ii_display_menu
- @dml_block_state = SelectedBlockMenuState.new
- @delegate_object[:block_name] = nil
-
- when :user_choice
- if @dml_link_state.block_name.present?
- # @prior_block_was_link = true
- @dml_block_state.block = blocks_find_by_block_name(@dml_blocks_in_file,
- @dml_link_state.block_name)
- @dml_link_state.block_name = nil
- else
- # puts "? - Select a block to execute (or type #{$texit} to exit):"
- break if inpseq_user_choice == :break # into @dml_block_state
- break if @dml_block_state.block.nil? # no block matched
- end
- # puts "! - Executing block: #{data}"
- @dml_block_state.block&.pub_name
-
- when :execute_block
- case (block_name = data)
- when item_back.pub_name
- debounce_reset
- @menu_user_clicked_back_link = true
- load_file_link_state = pop_link_history_and_trigger_load
- @dml_link_state = load_file_link_state.link_state
-
- InputSequencer.merge_link_state(
- @dml_link_state,
- InputSequencer.next_link_state(
- block_name: @dml_link_state.block_name,
- document_filename: @dml_link_state.document_filename,
- prior_block_was_link: true
- )
- )
-
- when item_edit.pub_name
- debounce_reset
- edited = edit_text(@dml_link_state.inherited_lines_block)
- @dml_link_state.inherited_lines = edited.split("\n") if edited
-
- return :break if pause_user_exit
-
- InputSequencer.next_link_state(prior_block_was_link: true)
-
- when item_history.pub_name
- debounce_reset
- files = history_files(@dml_link_state)
- files_table_rows = files.map do |file|
- if Regexp.new(@delegate_object[:saved_asset_match]) =~ file
- begin
- OpenStruct.new(
- file: file,
- row: format(
- @delegate_object[:saved_history_format],
- # create with default '*' so unknown parameters are given a wildcard
- $~.names.each_with_object(Hash.new('*')) do |name, hash|
- hash[name.to_sym] = $~[name]
- end
- )
- )
- rescue KeyError
- # pp $!, $@
- warn "Cannot format with: #{@delegate_object[:saved_history_format]}"
- error_handler('saved_history_format')
- break
- end
- else
- warn "Cannot parse name: #{file}"
- next
- end
- end&.compact
-
- return :break unless files_table_rows
-
- # repeat select+display until user exits
- row_attrib = :row
- loop do
- # menu with Back and Facet options at top
- case (name = prompt_select_code_filename(
- [@delegate_object[:prompt_filespec_back],
- @delegate_object[:prompt_filespec_facet]] +
- files_table_rows.map(&row_attrib),
- string: @delegate_object[:prompt_select_history_file],
- color_sym: :prompt_color_after_script_execution
- ))
- when @delegate_object[:prompt_filespec_back]
- break
- when @delegate_object[:prompt_filespec_facet]
- row_attrib = row_attrib == :row ? :file : :row
- else
- file = files_table_rows.select { |ftr| ftr.row == name }&.first
- info = file_info(file.file)
- warn "#{file.file} - #{info[:lines]} lines / #{info[:size]} bytes"
- warn(File.readlines(file.file,
- chomp: false).map.with_index do |line, ind|
- format(' %s. %s', format('% 4d', ind + 1).violet, line)
- end)
- end
- end
-
- return :break if pause_user_exit
-
- InputSequencer.next_link_state(prior_block_was_link: true)
-
- when item_load.pub_name
- debounce_reset
- sf = document_name_in_glob_as_file_name(@dml_link_state.document_filename,
- @delegate_object[:document_saved_lines_glob])
- load_filespec = load_filespec_from_expression(sf)
- if load_filespec
- @dml_link_state.inherited_lines_append(
- File.readlines(load_filespec, chomp: true)
- )
- end
-
- return :break if pause_user_exit
-
- InputSequencer.next_link_state(prior_block_was_link: true)
-
- when item_save.pub_name
- debounce_reset
- sf = document_name_in_glob_as_file_name(@dml_link_state.document_filename,
- @delegate_object[:document_saved_lines_glob])
- save_filespec = save_filespec_from_expression(sf)
- if save_filespec && !write_file_with_directory_creation(
- save_filespec,
- HashDelegator.join_code_lines(@dml_link_state.inherited_lines)
- )
- return :break
-
- end
-
- InputSequencer.next_link_state(prior_block_was_link: true)
-
- when item_shell.pub_name
- debounce_reset
- loop do
- command = prompt_for_command(":MDE #{Time.now.strftime('%FT%TZ')}> ".bgreen)
- break if !command.present? || command == 'exit'
-
- exit_status = execute_command_with_streams(
- [@delegate_object[:shell], '-c', command]
- )
- case exit_status
- when 0
- warn "#{'OK'.green} #{exit_status}"
- else
- warn "#{'ERR'.bred} #{exit_status}"
- end
- end
-
- return :break if pause_user_exit
-
- InputSequencer.next_link_state(prior_block_was_link: true)
-
- when item_view.pub_name
- debounce_reset
- warn @dml_link_state.inherited_lines_block
-
- return :break if pause_user_exit
-
- InputSequencer.next_link_state(prior_block_was_link: true)
-
- else
- @dml_block_state = block_state_for_name_from_cli(block_name)
- if @dml_block_state.block && @dml_block_state.block.shell == BlockType::OPTS
- debounce_reset
- link_state = LinkState.new
- options_state = read_show_options_and_trigger_reuse(
- link_state: link_state,
- mdoc: @dml_mdoc,
- selected: @dml_block_state.block
- )
-
- update_menu_base(options_state.options)
- options_state.load_file_link_state.link_state
- else
- inpseq_execute_block(block_name)
-
- if prompt_user_exit(block_name_from_cli: @run_state.source.block_name_from_cli,
- selected: @dml_block_state.block)
- return :break
- end
-
- ## order of block name processing: link block, cli, from user
- #
- @dml_link_state.block_name, @run_state.source.block_name_from_cli, cli_break =
- HashDelegator.next_link_state(
- block_name: @dml_link_state.block_name,
- block_name_from_cli: @dml_now_using_cli,
- block_state: @dml_block_state,
- was_using_cli: @dml_now_using_cli
- )
-
- if !@dml_block_state.source.block_name_from_ui && cli_break
- # &bsp '!block_name_from_ui + cli_break -> break'
- return :break
- end
-
- InputSequencer.next_link_state(
- block_name: @dml_link_state.block_name,
- prior_block_was_link: @dml_block_state.block.shell != BlockType::BASH
- )
- end
- end
-
- when :exit?
- data == $texit
- when :stay?
- data == $stay
- else
- raise "Invalid message: #{msg}"
- end
- end
- rescue StandardError
- HashDelegator.error_handler('document_inpseq',
- { abort: true })
+ @run_state.files.write_execution_output_to_file(@delegate_object[:logged_stdout_filespec])
end
# remove leading "./"
# replace characters: / : . * (space) with: (underscore)
def document_name_in_glob_as_file_name(document_filename, glob)
@@ -1490,13 +1265,13 @@
format(glob,
{ document_filename: document_filename.gsub(%r{^\./}, '').gsub(/[\/:\.\* ]/,
'_') })
end
- def dump_and_warn_block_state(selected:)
+ def dump_and_warn_block_state(name:, selected:)
if selected.nil?
- Exceptions.warn_format("Block not found -- name: #{@delegate_object[:block_name]}",
+ Exceptions.warn_format("Block not found -- name: #{name}",
{ abort: true })
end
return unless @delegate_object[:dump_selected_block]
@@ -1589,24 +1364,43 @@
temp_file.unlink
result_text
end
- def exec_bash_next_state(selected:, mdoc:, link_state:, block_source: {})
- lfls = execute_shell_type(
+ def execute_block_for_state_and_name(selected:, mdoc:, link_state:,
+ block_source: {})
+ lfls = execute_block_by_type_for_lfls(
selected: selected,
mdoc: mdoc,
link_state: link_state,
block_source: block_source
)
# if the same menu is being displayed, collect the display name of the selected menu item for use as the default item
[lfls.link_state,
- lfls.load_file == LoadFile::LOAD ? nil : selected.dname]
- #.tap { |ret| pp [__FILE__,__LINE__,'exec_bash_next_state()',ret] }
+ lfls.load_file == LoadFile::LOAD ? nil : selected.dname,
+ # 2024-08-22 true to quit
+ lfls.load_file == LoadFile::EXIT]
end
+ def execute_block_in_state(block_name)
+ @dml_block_state = block_state_for_name_from_cli(block_name)
+ dump_and_warn_block_state(name: block_name,
+ selected: @dml_block_state.block)
+ @dml_link_state, @dml_menu_default_dname, quit =
+ execute_block_for_state_and_name(
+ selected: @dml_block_state.block,
+ mdoc: @dml_mdoc,
+ link_state: @dml_link_state,
+ block_source: {
+ document_filename: @delegate_object[:filename],
+ time_now_date: Time.now.utc.strftime(@delegate_object[:shell_code_label_time_format])
+ }
+ )
+ :break if quit
+ end
+
# Executes a given command and processes its input, output, and error streams.
#
# @param [Array<String>] command the command to execute along with its arguments.
# @yield [stdin, stdout, stderr, thread] if a block is provided, it yields input, output, error lines, and the execution thread.
# @return [Integer] the exit status of the executed command (0 to 255).
@@ -1653,10 +1447,86 @@
end
exit_status
end
+ def execute_history_select(
+ files_table_rows,
+ exit_prompt: @delegate_object[:prompt_filespec_back],
+ pause_refresh: false,
+ stream:
+ )
+ # repeat select+display until user exits
+
+ pause_now = false
+ row_attrib = :row
+ loop do
+ if pause_now
+ break if prompt_select_continue == MenuState::EXIT
+ end
+
+ # menu with Back and Facet options at top
+ case (name = prompt_select_code_filename(
+ [exit_prompt,
+ @delegate_object[:prompt_filespec_facet]] +
+ files_table_rows.map(&row_attrib),
+ string: @delegate_object[:prompt_select_history_file],
+ color_sym: :prompt_color_after_script_execution
+ ))
+ when exit_prompt
+ break
+ when @delegate_object[:prompt_filespec_facet]
+ row_attrib = row_attrib == :row ? :file : :row
+ pause_now = false
+ else
+ file = files_table_rows.select { |ftr| ftr.row == name }&.first
+ info = file_info(file.file)
+ stream.puts "#{file.file} - #{info[:lines]} lines / " \
+ "#{info[:size]} bytes"
+ stream.puts(
+ File.readlines(file.file,
+ chomp: false).map.with_index do |line, ind|
+ format(' %s. %s',
+ AnsiString.new(format('% 4d', ind + 1)).send(:violet), line)
+ end
+ )
+ pause_now = pause_refresh
+ end
+ end
+ end
+
+ def execute_inherited_save
+ save_filespec = save_filespec_from_expression(
+ document_name_in_glob_as_file_name(
+ @dml_link_state.document_filename,
+ @delegate_object[:document_saved_lines_glob]
+ )
+ )
+ if save_filespec && !write_file_with_directory_creation(
+ save_filespec,
+ HashDelegator.join_code_lines(@dml_link_state.inherited_lines)
+ )
+ :break
+ end
+ end
+
+ def execute_navigate_back
+ @menu_user_clicked_back_link = true
+
+ keep_code = @dml_link_state.keep_code
+ inherited_lines = keep_code ? @dml_link_state.inherited_lines_block : nil
+
+ @dml_link_state = pop_link_history_new_state
+
+ {
+ block_name: @dml_link_state.block_name,
+ document_filename: @dml_link_state.document_filename,
+ inherited_lines: inherited_lines,
+ keep_code: keep_code
+ }
+ end
+
# 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.
@@ -1680,23 +1550,32 @@
# code to the clipboard or save it to a file.
#
# @param opts [Hash] Options hash containing configuration settings.
# @param mdoc [YourMDocClass] An instance of the MDoc class.
#
- def execute_shell_type(selected:, mdoc:, block_source:,
- link_state: LinkState.new)
+ def execute_block_by_type_for_lfls(selected:, mdoc:, block_source:,
+ link_state: LinkState.new)
if selected.shell == BlockType::LINK
debounce_reset
push_link_history_and_trigger_load(link_block_body: selected.body,
mdoc: mdoc,
selected: selected,
link_state: link_state,
block_source: block_source)
+ # from CLI
+ elsif selected.nickname == @delegate_object[:menu_option_exit_name][:line]
+ debounce_reset
+ LoadFileLinkState.new(LoadFile::EXIT, link_state)
+
elsif @menu_user_clicked_back_link
debounce_reset
- pop_link_history_and_trigger_load
+ # pop_link_history_new_state
+ LoadFileLinkState.new(
+ LoadFile::LOAD,
+ pop_link_history_new_state
+ )
elsif selected.shell == BlockType::OPTS
debounce_reset
code_lines = []
options_state = read_show_options_and_trigger_reuse(
@@ -1728,10 +1607,11 @@
elsif debounce_allows
compile_execute_and_trigger_reuse(mdoc: mdoc,
selected: selected,
link_state: link_state,
block_source: block_source)
+ LoadFileLinkState.new(LoadFile::REUSE, link_state)
else
LoadFileLinkState.new(LoadFile::REUSE, link_state)
end
end
@@ -1810,10 +1690,31 @@
# Expand expression if it contains format specifiers
def formatted_expression(expr)
expr.include?('%{') ? format_expression(expr) : expr
end
+ def fout_execution_report
+ @fout.fout fetch_color(data_sym: :execution_report_preview_head,
+ color_sym: :execution_report_preview_frame_color)
+ [
+ ['Block', @run_state.script_block_name],
+ ['Command', ([MarkdownExec::BIN_NAME, @delegate_object[:filename]] +
+ (@run_state.link_history.map { |item|
+ item[:block_name]
+ }) +
+ [@run_state.script_block_name]).join(' ')],
+ ['Script', @run_state.saved_filespec],
+ ['StdOut', @delegate_object[:logged_stdout_filespec]]
+ ].each do |label, value|
+ next unless value
+
+ output_labeled_value(label, value, DISPLAY_LEVEL_ADMIN)
+ end
+ @fout.fout fetch_color(data_sym: :execution_report_preview_tail,
+ color_sym: :execution_report_preview_frame_color)
+ end
+
def generate_temp_filename(ext = '.sh')
filename = begin
Dir::Tmpname.make_tmpname(['x', ext], nil)
rescue NoMethodError
require 'securerandom'
@@ -1927,61 +1828,22 @@
in_fenced_block: false,
headings: []
}
end
- def inpseq_execute_block(block_name)
- @dml_block_state = block_state_for_name_from_cli(block_name)
- dump_and_warn_block_state(selected: @dml_block_state.block)
- @dml_link_state, @dml_menu_default_dname =
- exec_bash_next_state(
- selected: @dml_block_state.block,
- mdoc: @dml_mdoc,
- link_state: @dml_link_state,
- block_source: {
- document_filename: @delegate_object[:filename],
- time_now_date: Time.now.utc.strftime(@delegate_object[:shell_code_label_time_format])
- }
- )
- end
-
- def inpseq_parse_document(_document_filename)
- @run_state.batch_index += 1
- @run_state.in_own_window = false
-
- # &bsp 'loop', block_name_from_cli, @cli_block_name
- @run_state.source.block_name_from_cli, @dml_now_using_cli, @dml_blocks_in_file, @dml_menu_blocks, @dml_mdoc =
- set_delobj_menu_loop_vars(block_name_from_cli: @run_state.source.block_name_from_cli,
- now_using_cli: @dml_now_using_cli,
- link_state: @dml_link_state)
- end
-
- def inpseq_user_choice
- @dml_block_state = load_cli_or_user_selected_block(all_blocks: @dml_blocks_in_file,
- menu_blocks: @dml_menu_blocks,
- default: @dml_menu_default_dname)
- # &bsp '@run_state.source.block_name_from_cli:',@run_state.source.block_name_from_cli
- if !@dml_block_state
- HashDelegator.error_handler('block_state missing', { abort: true })
- elsif @dml_block_state.state == MenuState::EXIT
- # &bsp 'load_cli_or_user_selected_block -> break'
- :break
- end
- 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])
state = initial_state
- selected_messages = yield :filter
+ selected_types = yield :filter
cfile.readlines(@delegate_object[:filename],
import_paths: @delegate_object[:import_paths]&.split(':')).each do |nested_line|
if nested_line
- update_line_and_block_state(nested_line, state, selected_messages,
+ update_line_and_block_state(nested_line, state, selected_types,
&block)
end
end
end
@@ -2025,47 +1887,53 @@
end
label_format_above = @delegate_object[:shell_code_label_format_above]
label_format_below = @delegate_object[:shell_code_label_format_below]
- [label_format_above && format(label_format_above,
- block_source.merge({ block_name: selected.pub_name }))] +
+ [label_format_above.present? &&
+ format(label_format_above,
+ block_source.merge({ block_name: selected.pub_name }))] +
output_lines.map do |line|
re = Regexp.new(link_block_data.fetch('pattern', '(?<line>.*)'))
next unless re =~ line
re.gsub_format(line,
link_block_data.fetch('format',
'%{line}'))
end.compact +
- [label_format_below && format(label_format_below,
- block_source.merge({ block_name: selected.pub_name }))]
+ [label_format_below.present? &&
+ format(label_format_below,
+ block_source.merge({ block_name: selected.pub_name }))]
end
def link_history_push_and_next(
curr_block_name:, curr_document_filename:,
inherited_block_names:, inherited_dependencies:, inherited_lines:,
+ keep_code:,
next_block_name:, next_document_filename:,
+ next_keep_code:,
next_load_file:
)
@link_history.push(
LinkState.new(
block_name: curr_block_name,
document_filename: curr_document_filename,
inherited_block_names: inherited_block_names,
inherited_dependencies: inherited_dependencies,
- inherited_lines: inherited_lines
+ inherited_lines: inherited_lines,
+ keep_code: keep_code
)
)
LoadFileLinkState.new(
next_load_file,
LinkState.new(
block_name: next_block_name,
document_filename: next_document_filename,
inherited_block_names: inherited_block_names,
inherited_dependencies: inherited_dependencies,
- inherited_lines: inherited_lines
+ inherited_lines: inherited_lines,
+ keep_code: next_keep_code
)
)
end
def link_load_format_data
@@ -2117,12 +1985,10 @@
source = OpenStruct.new(block_name_from_ui: true)
state = block_state.state
end
SelectedBlockMenuState.new(block, source, state)
- rescue StandardError
- HashDelegator.error_handler('load_cli_or_user_selected_block')
end
# format + glob + select for file in load block
# name has references to ENV vars and doc and batch vars incl. timestamp
def load_filespec_from_expression(expression)
@@ -2159,10 +2025,27 @@
name
end
end
end
+ def manage_cli_selection_state(block_name_from_cli:, now_using_cli:,
+ link_state:)
+ 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 =
+ @cli_block_name = nil
+ end
+
+ @delegate_object = @menu_base_options.dup
+ @menu_user_clicked_back_link = false
+ [block_name_from_cli, now_using_cli]
+ end
+
def mdoc_and_blocks_from_nested_files
menu_blocks = blocks_from_nested_files
mdoc = MDoc.new(menu_blocks) do |nopts|
@delegate_object.merge!(nopts)
end
@@ -2182,10 +2065,11 @@
menu_blocks = mdoc.fcbs_per_options(@delegate_object)
add_menu_chrome_blocks!(menu_blocks: menu_blocks, link_state: link_state)
### compress empty lines
HashDelegator.delete_consecutive_blank_lines!(menu_blocks)
+ HashDelegator.tables_into_columns!(menu_blocks, @delegate_object)
[all_blocks, menu_blocks, mdoc] # &br
end
def menu_add_disabled_option(name)
raise unless name.present?
@@ -2238,52 +2122,10 @@
else
option_value
end
end
- def menu_enable_option(name, count, type, menu_state: MenuState::LOAD)
- raise unless name.present?
- raise if @dml_menu_blocks.nil?
-
- item = @dml_menu_blocks.find { |block| block.oname == name }
-
- # create menu item when it is needed (count > 0)
- #
- if item.nil? && count.positive?
- item = append_chrome_block(menu_blocks: @dml_menu_blocks,
- menu_state: menu_state)
- end
-
- # update item if it exists
- #
- return unless item
-
- item.dname = type.present? ? "#{name} (#{count} #{type})" : name
- if count.positive?
- item.delete(:disabled)
- else
- item[:disabled] = ''
- end
- end
-
- def manage_cli_selection_state(block_name_from_cli:, now_using_cli:,
- link_state:)
- 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 =
- @cli_block_name = nil
- end
-
- @delegate_object = @menu_base_options.dup
- @menu_user_clicked_back_link = false
- [block_name_from_cli, now_using_cli]
- 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
@@ -2307,43 +2149,24 @@
curr_block_name: selected.pub_name,
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: HashDelegator.code_merge(code_lines),
+ keep_code: link_state&.keep_code,
next_block_name: '',
next_document_filename: @delegate_object[:filename],
+ next_keep_code: false,
next_load_file: LoadFile::REUSE
)
end
def output_color_formatted(data_sym, color_sym)
formatted_string = string_send_color(@delegate_object[data_sym],
color_sym)
@fout.fout formatted_string
end
- def fout_execution_report
- @fout.fout fetch_color(data_sym: :execution_report_preview_head,
- color_sym: :execution_report_preview_frame_color)
- [
- ['Block', @run_state.script_block_name],
- ['Command', ([MarkdownExec::BIN_NAME, @delegate_object[:filename]] +
- (@run_state.link_history.map { |item|
- item[:block_name]
- }) +
- [@run_state.script_block_name]).join(' ')],
- ['Script', @run_state.saved_filespec],
- ['StdOut', @delegate_object[:logged_stdout_filespec]]
- ].each do |label, value|
- next unless value
-
- output_labeled_value(label, value, DISPLAY_LEVEL_ADMIN)
- end
- @fout.fout fetch_color(data_sym: :execution_report_preview_tail,
- color_sym: :execution_report_preview_frame_color)
- end
-
def output_execution_summary
return unless @delegate_object[:output_execution_summary]
@fout.fout_section 'summary', {
execute_aborted_at: @run_state.aborted_at,
@@ -2399,33 +2222,32 @@
((link_state&.inherited_block_names || []) + block_names).sort.uniq,
inherited_dependencies:
(link_state&.inherited_dependencies || {}).merge(dependencies || {}), ### merge, not replace, key data
inherited_lines:
HashDelegator.code_merge(link_state&.inherited_lines, code_lines),
+ keep_code: link_state&.keep_code,
next_block_name: next_block_name,
next_document_filename: @delegate_object[:filename], # not next_document_filename
+ next_keep_code: false,
next_load_file: LoadFile::REUSE # not next_document_filename == @delegate_object[:filename] ? LoadFile::REUSE : LoadFile::LOAD
)
# LoadFileLinkState.new(LoadFile::REUSE, link_state)
end
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.
+ # It updates the history state for the next block.
#
- # @return [LoadFileLinkState] An object indicating the action to load the next block.
- def pop_link_history_and_trigger_load
+ # @return [LinkState] An object indicating the state for the next block.
+ def pop_link_history_new_state
pop = @link_history.pop
peek = @link_history.peek
- LoadFileLinkState.new(
- LoadFile::LOAD,
- LinkState.new(
- document_filename: pop.document_filename,
- inherited_block_names: peek.inherited_block_names,
- inherited_dependencies: peek.inherited_dependencies,
- inherited_lines: peek.inherited_lines
- )
+ LinkState.new(
+ document_filename: pop.document_filename,
+ inherited_block_names: peek.inherited_block_names,
+ inherited_dependencies: peek.inherited_dependencies,
+ inherited_lines: peek.inherited_lines
)
end
def post_execution_process
do_save_execution_output
@@ -2530,12 +2352,10 @@
return false if sel == @delegate_object[:prompt_no]
return true if sel == @delegate_object[:prompt_yes]
@allowed_execution_block = @prior_execution_block
true
- rescue TTY::Reader::InputInterrupt
- exit 1
end
def prompt_for_command(prompt)
print prompt
@@ -2600,12 +2420,10 @@
elsif sel == MenuOptions::SAVE_SCRIPT
save_to_file(required_lines: required_lines, selected: selected)
end
sel == MenuOptions::YES
- rescue TTY::Reader::InputInterrupt
- exit 1
end
# public
def prompt_select_code_filename(
@@ -2630,12 +2448,10 @@
else
menu.choice filename
end
end
end
- rescue TTY::Reader::InputInterrupt
- exit 1
end
def prompt_select_continue(filter: true, quiet: true)
sel = @prompt.select(
string_send_color(@delegate_object[:prompt_after_script_execution],
@@ -2645,12 +2461,10 @@
) do |menu|
menu.choice @delegate_object[:prompt_yes]
menu.choice @delegate_object[:prompt_exit]
end
sel == @delegate_object[:prompt_exit] ? MenuState::EXIT : MenuState::CONTINUE
- rescue TTY::Reader::InputInterrupt
- exit 1
end
# user prompt to exit if the menu will be displayed again
#
def prompt_user_exit(block_name_from_cli:, selected:)
@@ -2727,20 +2541,23 @@
if link_block_data[LinkKeys::RETURN]
pop_add_current_code_to_head_and_trigger_load(link_state, block_names, code_lines,
dependencies, selected, next_block_name: next_block_name)
else
+ next_keep_code = link_state&.keep_code || link_block_data.fetch('keep', false) #/*LinkKeys::KEEP*/
link_history_push_and_next(
curr_block_name: selected.pub_name,
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: HashDelegator.code_merge(
link_state&.inherited_lines, code_lines
),
+ keep_code: link_state&.keep_code,
next_block_name: next_block_name,
next_document_filename: next_document_filename,
+ next_keep_code: next_keep_code,
next_load_file: next_document_filename == @delegate_object[:filename] ? LoadFile::REUSE : LoadFile::LOAD
)
end
end
@@ -2751,25 +2568,53 @@
{ expr: filespec })
puts @delegate_object[:prompt_enter_filespec]
gets.chomp
end
+ def read_saved_assets_for_history_table
+ files = history_files(@dml_link_state).sort
+ files.map do |file|
+ if Regexp.new(@delegate_object[:saved_asset_match]) =~ file
+ begin
+ OpenStruct.new(
+ file: file,
+ row: format(
+ @delegate_object[:saved_history_format],
+ # create with default '*' so unknown parameters are given a wildcard
+ $~.names.each_with_object(Hash.new('*')) do |name, hash|
+ hash[name.to_sym] = $~[name]
+ end
+ )
+ )
+ rescue KeyError
+ # pp $!, $@
+ warn "Cannot format with: #{@delegate_object[:saved_history_format]}"
+ error_handler('saved_history_format')
+ return nil
+ end
+ else
+ warn "Cannot parse name: #{file}"
+ next
+ end
+ end&.compact
+ 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:,
mdoc:, link_state: LinkState.new)
obj = {}
# concatenated body of all required blocks loaded a YAML
- data = YAML.load(
+ data = (YAML.load(
collect_required_code_lines(
mdoc: mdoc, selected: selected,
link_state: link_state, block_source: {}
).join("\n")
- ).transform_keys(&:to_sym)
+ ) || {}).transform_keys(&:to_sym)
if selected.shell == BlockType::OPTS
obj = data
else
(data || []).each do |key, value|
@@ -2845,18 +2690,23 @@
def runtime_exception(exception_sym, name, items)
if @delegate_object[exception_sym] != 0
data = { name: name, detail: items.join(', ') }
warn(
- format(
- @delegate_object.fetch(:exception_format_name, "\n%{name}"),
- data
- ).send(@delegate_object.fetch(:exception_color_name, :red)) +
- format(
- @delegate_object.fetch(:exception_format_detail, " - %{detail}\n"),
- data
- ).send(@delegate_object.fetch(:exception_color_detail, :yellow))
+ AnsiString.new(format(
+ @delegate_object.fetch(:exception_format_name,
+ "\n%{name}"),
+ data
+ )).send(@delegate_object.fetch(:exception_color_name,
+ :red)) +
+ AnsiString.new(format(
+ @delegate_object.fetch(:exception_format_detail,
+ " - %{detail}\n"),
+ data
+ )).send(@delegate_object.fetch(
+ :exception_color_detail, :yellow
+ ))
)
end
return unless (@delegate_object[exception_sym]).positive?
exit @delegate_object[exception_sym]
@@ -2905,10 +2755,28 @@
def save_to_file(required_lines:, selected:)
write_command_file(required_lines: required_lines, selected: selected)
@fout.fout "File saved: #{@run_state.saved_filespec}"
end
+ def select_document_if_multiple(options, files, prompt:)
+ # binding.irb
+ return files if files.class == String ###
+ return files[0] if (count = files.count) == 1
+
+ return unless count >= 2
+
+ opts = options.dup
+ select_option_or_exit(
+ string_send_color(
+ prompt,
+ :prompt_color_after_script_execution
+ ),
+ files,
+ opts.merge(per_page: opts[:select_page_height])
+ )
+ end
+
# Presents a TTY prompt to select an option or exit, returns metadata including option and selected
def select_option_with_metadata(prompt_text, menu_items, opts = {})
## configure to environment
#
register_console_attributes(opts)
@@ -2946,46 +2814,12 @@
else
selected.selected = selection
end
selected
- rescue TTY::Reader::InputInterrupt
- exit 1
- rescue StandardError
- HashDelegator.error_handler('select_option_with_metadata')
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.
- #
- # @param link_state [LinkState] The current link state object.
- # @param block_name_from_cli [Boolean] Indicates if the block name is from CLI.
- def set_delob_filename_block_name(link_state:, block_name_from_cli:)
- @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
-
- def set_delobj_menu_loop_vars(block_name_from_cli:, now_using_cli:,
- link_state:)
- block_name_from_cli, now_using_cli =
- manage_cli_selection_state(block_name_from_cli: block_name_from_cli,
- now_using_cli: now_using_cli,
- link_state: link_state)
- set_delob_filename_block_name(link_state: link_state,
- block_name_from_cli: 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(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
-
def set_environment_variables_for_block(selected)
code_lines = []
YAML.load(selected.body.join("\n"))&.each do |key, value|
ENV[key] = value.to_s
@@ -3066,11 +2900,11 @@
reqs: reqs,
shell: fcb_title_groups.fetch(:shell, ''),
stdin: if (tn = rest.match(/<(?<type>\$)?(?<name>[A-Za-z_-]\S+)/))
tn.named_captures.sym_keys
end,
- stdout: if (tn = rest.match(/>(?<type>\$)?(?<name>[A-Za-z_\-.\w]+)/))
+ stdout: if (tn = rest.match(/>(?<type>\$)?(?<name>[\w.\-]+)/))
tn.named_captures.sym_keys
end,
title: title,
wraps: wraps
)
@@ -3091,34 +2925,34 @@
# 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.
# @param state [Hash] The current state of the parser, including flags and data related to the processing.
# @param opts [Hash] A hash containing various options for line and block processing.
- # @param selected_messages [Array<String>] Accumulator for lines or messages that are subject to further processing.
+ # @param selected_types [Array<String>] Accumulator for lines or messages that are subject to further processing.
# @param block [Proc] An optional block for further processing or transformation of lines.
#
# @option state [Array<String>] :headings Current headings to be updated based on the line.
# @option state [Regexp] :fenced_start_and_end_regex Regular expression to match the start and end of a fenced block.
# @option state [Boolean] :in_fenced_block Flag indicating whether the current line is inside a fenced block.
# @option state [Object] :fcb An object representing the current fenced code block being processed.
#
# @option opts [Boolean] :menu_blocks_with_headings Flag indicating whether to update headings while processing.
#
- # @return [Void] The function modifies the `state` and `selected_messages` arguments in place.
+ # @return [Void] The function modifies the `state` and `selected_types` arguments in place.
##
- def update_line_and_block_state(nested_line, state, selected_messages,
+ def update_line_and_block_state(nested_line, state, selected_types,
&block)
line = nested_line.to_s
if line.match(@delegate_object[:fenced_start_and_end_regex])
if state[:in_fenced_block]
## end of code block
#
HashDelegator.update_menu_attrib_yield_selected(
fcb: state[:fcb],
- messages: selected_messages,
+ messages: selected_types,
configuration: @delegate_object,
- &block
+ &block
)
state[:in_fenced_block] = false
else
## start of code block
#
@@ -3136,25 +2970,479 @@
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
#
- HashDelegator.yield_line_if_selected(line, selected_messages, &block)
+ HashDelegator.yield_line_if_selected(line, selected_types, &block)
else
# &bsp 'line is not recognized for block state'
end
end
## apply options to current state
#
def update_menu_base(options)
- @menu_base_options.merge!(options)
+ # under simple uses, @menu_base_options may be nil
+ @menu_base_options&.merge!(options)
@delegate_object.merge!(options)
end
+ def vux_await_user_selection
+ @dml_block_state = load_cli_or_user_selected_block(all_blocks: @dml_blocks_in_file,
+ menu_blocks: @dml_menu_blocks,
+ default: @dml_menu_default_dname)
+ # &bsp '@run_state.source.block_name_from_cli:',@run_state.source.block_name_from_cli
+ if !@dml_block_state
+ HashDelegator.error_handler('block_state missing', { abort: true })
+ elsif @dml_block_state.state == MenuState::EXIT
+ # &bsp 'load_cli_or_user_selected_block -> break'
+ :break
+ end
+ end
+
+ def vux_clear_menu_state
+ @dml_block_state = SelectedBlockMenuState.new
+ @delegate_object[:block_name] = nil
+ end
+
+ def vux_edit_inherited
+ edited = edit_text(@dml_link_state.inherited_lines_block)
+ @dml_link_state.inherited_lines = edited.split("\n") if edited
+ end
+
+ def vux_execute_and_prompt(block_name)
+ @dml_block_state = block_state_for_name_from_cli(block_name)
+ if @dml_block_state.block && @dml_block_state.block.shell == BlockType::OPTS
+ debounce_reset
+ link_state = LinkState.new
+ options_state = read_show_options_and_trigger_reuse(
+ link_state: link_state,
+ mdoc: @dml_mdoc,
+ selected: @dml_block_state.block
+ )
+
+ update_menu_base(options_state.options)
+ options_state.load_file_link_state.link_state
+ return
+ end
+
+ return :break if execute_block_in_state(block_name) == :break
+
+ if prompt_user_exit(block_name_from_cli: @run_state.source.block_name_from_cli,
+ selected: @dml_block_state.block)
+ return :break
+ end
+
+ ## order of block name processing: link block, cli, from user
+ #
+ @dml_link_state.block_name, @run_state.source.block_name_from_cli, cli_break =
+ HashDelegator.next_link_state(
+ block_name: @dml_link_state.block_name,
+ block_name_from_cli: @dml_now_using_cli,
+ block_state: @dml_block_state,
+ was_using_cli: @dml_now_using_cli
+ )
+
+ # &bsp '!block_name_from_ui + cli_break -> break'
+ !@dml_block_state.source.block_name_from_ui && cli_break && :break
+ end
+
+ def vux_execute_block_per_type(block_name, formatted_choice_ostructs)
+ case block_name
+ when formatted_choice_ostructs[:back].pub_name
+ debounce_reset
+ vux_navigate_back_for_ls
+
+ when formatted_choice_ostructs[:edit].pub_name
+ debounce_reset
+ vux_edit_inherited
+ return :break if pause_user_exit
+
+ InputSequencer.next_link_state(prior_block_was_link: true)
+
+ when formatted_choice_ostructs[:history].pub_name
+ debounce_reset
+ files_table_rows = read_saved_assets_for_history_table
+ return :break unless files_table_rows
+
+ execute_history_select(files_table_rows, stream: $stderr)
+ return :break if pause_user_exit
+
+ InputSequencer.next_link_state(prior_block_was_link: true)
+
+ when formatted_choice_ostructs[:load].pub_name
+ debounce_reset
+ vux_load_inherited
+ return :break if pause_user_exit
+
+ InputSequencer.next_link_state(prior_block_was_link: true)
+
+ when formatted_choice_ostructs[:save].pub_name
+ debounce_reset
+ return :break if execute_inherited_save == :break
+
+ InputSequencer.next_link_state(prior_block_was_link: true)
+
+ when formatted_choice_ostructs[:shell].pub_name
+ debounce_reset
+ vux_input_and_execute_shell_commands(stream: $stderr)
+ return :break if pause_user_exit
+
+ InputSequencer.next_link_state(prior_block_was_link: true)
+
+ when formatted_choice_ostructs[:view].pub_name
+ debounce_reset
+ vux_view_inherited(stream: $stderr)
+ return :break if pause_user_exit
+
+ InputSequencer.next_link_state(prior_block_was_link: true)
+
+ else
+ return :break if vux_execute_and_prompt(block_name) == :break
+
+ InputSequencer.next_link_state(
+ block_name: @dml_link_state.block_name,
+ prior_block_was_link: @dml_block_state.block.shell != BlockType::BASH
+ )
+ end
+ end
+
+ def vux_formatted_names_for_state_chrome_blocks(
+ names: %w[back edit history load save shell view]
+ )
+ names.each_with_object({}) do |name, result|
+ do_key = :"menu_option_#{name}_name"
+ oname = HashDelegator.safeval(@delegate_object[do_key])
+ dname = format(@delegate_object[:menu_link_format], oname)
+ result[name.to_sym] = OpenStruct.new(
+ dname: dname,
+ name: dname,
+ oname: dname,
+ pub_name: dname.pub_name
+ )
+ end
+ end
+
+ def vux_init
+ @menu_base_options = @delegate_object
+ @dml_link_state = LinkState.new(
+ block_name: @delegate_object[:block_name],
+ document_filename: @delegate_object[:filename]
+ )
+ @run_state.source.block_name_from_cli = @dml_link_state.block_name.present?
+ @cli_block_name = @dml_link_state.block_name
+ @dml_now_using_cli = @run_state.source.block_name_from_cli
+ @dml_menu_default_dname = nil
+ @dml_block_state = SelectedBlockMenuState.new
+ @doc_saved_lines_files = []
+
+ @run_state.batch_random = Random.new.rand
+ @run_state.batch_index = 0
+
+ @run_state.files = StreamsOut.new
+ end
+
+ def vux_input_and_execute_shell_commands(stream:)
+ loop do
+ command = prompt_for_command(AnsiString.new(":MDE #{Time.now.strftime('%FT%TZ')}> ").send(:bgreen))
+ break if !command.present? || command == 'exit'
+
+ exit_status = execute_command_with_streams(
+ [@delegate_object[:shell], '-c', command]
+ )
+ case exit_status
+ when 0
+ stream.puts "#{'OK'.green} #{exit_status}"
+ else
+ stream.puts "#{'ERR'.bred} #{exit_status}"
+ end
+ end
+ end
+
+ ## load file with code lines per options
+ #
+ def vux_load_code_files_into_state
+ return unless @menu_base_options[:load_code].present?
+
+ @dml_link_state.inherited_lines =
+ @menu_base_options[:load_code].split(':').map do |path|
+ File.readlines(path, chomp: true)
+ end.flatten(1)
+
+ inherited_block_names = []
+ inherited_dependencies = {}
+ selected = FCB.new(oname: 'load_code')
+ pop_add_current_code_to_head_and_trigger_load(
+ @dml_link_state, inherited_block_names,
+ code_lines, inherited_dependencies, selected
+ )
+ end
+
+ def vux_load_inherited
+ sf = document_name_in_glob_as_file_name(@dml_link_state.document_filename,
+ @delegate_object[:document_saved_lines_glob])
+ load_filespec = load_filespec_from_expression(sf)
+ return unless load_filespec
+
+ @dml_link_state.inherited_lines_append(
+ File.readlines(load_filespec, chomp: true)
+ )
+ end
+
+ # Select and execute a code block from a Markdown document.
+ #
+ # This method allows the user to interactively select a code block from a
+ # 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 vux_main_loop
+ vux_init
+ vux_load_code_files_into_state
+ formatted_choice_ostructs = vux_formatted_names_for_state_chrome_blocks
+
+ block_list = [@delegate_object[:block_name]].select(&:present?).compact + @delegate_object[:input_cli_rest]
+ @delegate_object[:block_name] = nil
+
+ process_commands(
+ arguments: @p_all_arguments,
+ named_procs: yield(:command_names, @delegate_object),
+ options_parsed: @p_options_parsed,
+ rest: @p_rest,
+ enable_search: @delegate_object[:default_find_select_open]
+ ) do |type, data|
+ case type
+ when ArgPro::ActSetBlockName
+ @delegate_object[:block_name] = data
+ @delegate_object[:input_cli_rest] = ''
+ when ArgPro::ConvertValue
+ # call for side effects, output, or exit
+ data[0].call(data[1])
+ when ArgPro::ActFileIsMissing
+ raise FileMissingError, data, caller
+ when ArgPro::ActFind
+ find_value(data, execute_chosen_found: true)
+ when ArgPro::ActSetFileName
+ @delegate_object[:filename] = data
+ when ArgPro::ActSetPath
+ @delegate_object[:path] = data
+ when ArgPro::CallProcess
+ yield :call_proc, [@delegate_object, data]
+ when ArgPro::ActSetOption
+ @delegate_object[data[0]] = data[1]
+ else
+ raise
+ end
+ end
+
+ InputSequencer.new(
+ @delegate_object[:filename],
+ block_list
+ ).run do |msg, data|
+ # &bt msg
+ case msg
+ when :parse_document # once for each menu
+ vux_parse_document
+ vux_menu_append_history_files(formatted_choice_ostructs)
+ vux_publish_document_file_name_for_external_automation
+
+ when :display_menu
+ vux_clear_menu_state
+
+ when :user_choice
+ vux_user_selected_block_name
+
+ when :execute_block
+ ret = vux_execute_block_per_type(data, formatted_choice_ostructs)
+ vux_publish_block_name_for_external_automation(data)
+ ret
+
+ when :close_ux
+ if @vux_pipe_open.present? && File.exist?(@vux_pipe_open)
+ @vux_pipe_open.close
+ @vux_pipe_open = nil
+ end
+ if @vux_pipe_created.present? && File.exist?(@vux_pipe_created)
+ File.delete(@vux_pipe_created)
+ @vux_pipe_created = nil
+ end
+
+ when :exit?
+ data == $texit
+
+ when :stay?
+ data == $stay
+
+ else
+ raise "Invalid message: #{msg}"
+
+ end
+ end
+ end
+
+ def vux_menu_append_history_files(formatted_choice_ostructs)
+ if @delegate_object[:menu_for_history]
+ history_files(@dml_link_state).tap do |files|
+ if files.count.positive?
+ dml_menu_append_chrome_item(
+ formatted_choice_ostructs[:history].oname, files.count,
+ 'files', menu_state: MenuState::HISTORY
+ )
+ end
+ end
+ end
+
+ return unless @delegate_object[:menu_for_saved_lines] && @delegate_object[:document_saved_lines_glob].present?
+
+ sf = document_name_in_glob_as_file_name(
+ @dml_link_state.document_filename,
+ @delegate_object[:document_saved_lines_glob]
+ )
+ files = sf ? Dir.glob(sf) : []
+ @doc_saved_lines_files = files.count.positive? ? files : []
+
+ lines_count = @dml_link_state.inherited_lines_count
+
+ # add menu items (glob, load, save) and enable selectively
+ if files.count.positive? || lines_count.positive?
+ menu_add_disabled_option(sf)
+ end
+ if files.count.positive?
+ dml_menu_append_chrome_item(formatted_choice_ostructs[:load].dname, files.count, 'files',
+ menu_state: MenuState::LOAD)
+ end
+ if @delegate_object[:menu_inherited_lines_edit_always] || lines_count.positive?
+ dml_menu_append_chrome_item(formatted_choice_ostructs[:edit].dname, lines_count, 'lines',
+ menu_state: MenuState::EDIT)
+ end
+ if lines_count.positive?
+ dml_menu_append_chrome_item(formatted_choice_ostructs[:save].dname, 1, '',
+ menu_state: MenuState::SAVE)
+ end
+ if lines_count.positive?
+ dml_menu_append_chrome_item(formatted_choice_ostructs[:view].dname, 1, '',
+ menu_state: MenuState::VIEW)
+ end
+ # rubocop:disable Style/GuardClause
+ if @delegate_object[:menu_with_shell]
+ dml_menu_append_chrome_item(formatted_choice_ostructs[:shell].dname, 1, '',
+ menu_state: MenuState::SHELL)
+ end
+ # rubocop:enable Style/GuardClause
+
+ # # reflect new menu items
+ # @dml_mdoc = MDoc.new(@dml_menu_blocks)
+ end
+
+ def vux_navigate_back_for_ls
+ InputSequencer.merge_link_state(
+ @dml_link_state,
+ InputSequencer.next_link_state(
+ **execute_navigate_back.merge(prior_block_was_link: true)
+ )
+ )
+ end
+
+ def vux_parse_document
+ @run_state.batch_index += 1
+ @run_state.in_own_window = false
+
+ @run_state.source.block_name_from_cli, @dml_now_using_cli =
+ manage_cli_selection_state(
+ block_name_from_cli: @run_state.source.block_name_from_cli,
+ now_using_cli: @dml_now_using_cli,
+ link_state: @dml_link_state
+ )
+
+ @delegate_object[:filename] = @dml_link_state.document_filename
+ @dml_link_state.block_name = @delegate_object[:block_name] =
+ @run_state.source.block_name_from_cli ?
+ @cli_block_name :
+ @dml_link_state.block_name
+
+ # update @delegate_object and @menu_base_options in auto_load
+ #
+ @dml_blocks_in_file, @dml_menu_blocks, @dml_mdoc =
+ mdoc_menu_and_blocks_from_nested_files(@dml_link_state)
+ dump_delobj(@dml_blocks_in_file, @dml_menu_blocks, @dml_link_state)
+ # &bsp 'loop', @run_state.source.block_name_from_cli, @cli_block_name
+ end
+
+ def publish_for_external_automation(message:)
+ return if @delegate_object[:publish_document_file_name].empty?
+
+ pipe_path = absolute_path(@delegate_object[:publish_document_file_name])
+
+ case @delegate_object[:publish_document_file_mode]
+ when 'append'
+ File.write(pipe_path, message + "\n", mode: 'a')
+ when 'fifo'
+ unless @vux_pipe_open
+ unless File.exist?(pipe_path)
+ FileUtils.mkfifo(pipe_path)
+ @vux_pipe_created = pipe_path
+ end
+ @vux_pipe_open = File.open(pipe_path, 'w')
+ end
+ @vux_pipe_open.puts(message + "\n")
+ @vux_pipe_open.flush
+ when 'write'
+ File.write(pipe_path, message)
+ else
+ raise 'Invalid publish_document_file_mode:' \
+ " #{@delegate_object[:publish_document_file_mode]}"
+ end
+ end
+
+ def vux_publish_block_name_for_external_automation(block_name)
+ publish_for_external_automation(
+ message: format(
+ @delegate_object[:publish_block_name_format],
+ { block: block_name,
+ document: @delegate_object[:filename],
+ time: Time.now.utc.strftime(
+ @delegate_object[:publish_time_format]
+ ) }
+ )
+ )
+ end
+
+ def vux_publish_document_file_name_for_external_automation
+ return unless @delegate_object[:publish_document_file_name].present?
+
+ publish_for_external_automation(
+ message: format(
+ @delegate_object[:publish_document_name_format],
+ { document: @delegate_object[:filename],
+ time: Time.now.utc.strftime(
+ @delegate_object[:publish_time_format]
+ ) }
+ )
+ )
+ end
+
+ # return :break to break from loop
+ def vux_user_selected_block_name
+ if @dml_link_state.block_name.present?
+ # @prior_block_was_link = true
+ @dml_block_state.block = blocks_find_by_block_name(@dml_blocks_in_file,
+ @dml_link_state.block_name)
+ @dml_link_state.block_name = nil
+ else
+ # puts "? - Select a block to execute (or type #{$texit} to exit):"
+ return :break if vux_await_user_selection == :break # into @dml_block_state
+ return :break if @dml_block_state.block.nil? # no block matched
+ end
+ # puts "! - Executing block: #{data}"
+ @dml_block_state.block&.pub_name
+ end
+
+ def vux_view_inherited(stream:)
+ stream.puts @dml_link_state.inherited_lines_block
+ end
+
def wait_for_stream_processing
@process_mutex.synchronize do
@process_cv.wait(@process_mutex)
end
rescue Interrupt
@@ -3163,15 +3451,17 @@
def wait_for_user_selected_block(all_blocks, menu_blocks, default)
block_state = wait_for_user_selection(all_blocks, menu_blocks, default)
handle_back_or_continue(block_state)
block_state
- rescue StandardError
- HashDelegator.error_handler('wait_for_user_selected_block')
end
def wait_for_user_selection(_all_blocks, menu_blocks, default)
+ if @delegate_object[:clear_screen_for_select_block]
+ printf("\e[1;1H\e[2J")
+ end
+
prompt_title = string_send_color(
@delegate_object[:prompt_select_block].to_s, :prompt_color_after_script_execution
)
menu_items = prepare_blocks_menu(menu_blocks)
@@ -3821,53 +4111,51 @@
def setup
@hd = HashDelegator.new
@hd.instance_variable_set(:@run_state, mock('run_state'))
end
- def test_format_execution_stream_with_valid_key
- result = HashDelegator.format_execution_stream(
- { stdout: %w[output1 output2] },
- ExecutionStreams::STD_OUT
- )
+ # def test_format_execution_stream_with_valid_key
+ # result = HashDelegator.format_execution_stream(
+ # { stdout: %w[output1 output2] },
+ # ExecutionStreams::STD_OUT
+ # )
- assert_equal "output1\noutput2", result
- end
+ # assert_equal "output1\noutput2", result
+ # end
- def test_format_execution_stream_with_empty_key
- @hd.instance_variable_get(:@run_state).stubs(:files).returns({})
+ # def test_format_execution_stream_with_empty_key
+ # @hd.instance_variable_get(:@run_state).stubs(:files).returns({})
- result = HashDelegator.format_execution_stream(nil,
- ExecutionStreams::STD_ERR)
+ # result = HashDelegator.format_execution_stream(nil,
+ # ExecutionStreams::STD_ERR)
- assert_equal '', result
- end
+ # assert_equal '', result
+ # end
- def test_format_execution_stream_with_nil_files
- @hd.instance_variable_get(:@run_state).stubs(:files).returns(nil)
+ # def test_format_execution_stream_with_nil_files
+ # @hd.instance_variable_get(:@run_state).stubs(:files).returns(nil)
- result = HashDelegator.format_execution_stream(nil, :stdin)
+ # result = HashDelegator.format_execution_stream(nil, :stdin)
- assert_equal '', result
- end
+ # assert_equal '', result
+ # end
end
class TestHashDelegatorHandleBackLink < Minitest::Test
def setup
@hd = HashDelegator.new
@hd.stubs(:history_state_pop)
end
- def test_pop_link_history_and_trigger_load
+ def test_pop_link_history_new_state
# Verifying that history_state_pop is called
# @hd.expects(:history_state_pop).once
- result = @hd.pop_link_history_and_trigger_load
+ result = @hd.pop_link_history_new_state
- # Asserting the result is an instance of LoadFileLinkState
- assert_instance_of LoadFileLinkState, result
- assert_equal LoadFile::LOAD, result.load_file
- assert_nil result.link_state.block_name
+ # Asserting the result is an instance of LinkState
+ assert_nil result.block_name
end
end
class TestHashDelegatorHandleBlockState < Minitest::Test
def setup
@@ -3950,11 +4238,11 @@
class TestHashDelegatorHandleStream < Minitest::Test
def setup
@hd = HashDelegator.new
@hd.instance_variable_set(:@run_state,
- OpenStruct.new(files: { stdout: [] }))
+ OpenStruct.new(files: StreamsOut.new))
@hd.instance_variable_set(:@delegate_object,
{ output_stdout: true })
end
def test_handle_stream
@@ -3962,13 +4250,12 @@
file_type = ExecutionStreams::STD_OUT
Thread.new { @hd.handle_stream(stream: stream, file_type: file_type) }
@hd.wait_for_stream_processing
-
assert_equal ['line 1', 'line 2'],
- @hd.instance_variable_get(:@run_state).files[ExecutionStreams::STD_OUT]
+ @hd.instance_variable_get(:@run_state).files.stream_lines(ExecutionStreams::STD_OUT)
end
def test_handle_stream_with_io_error
stream = StringIO.new("line 1\nline 2\n")
file_type = ExecutionStreams::STD_OUT
@@ -3977,11 +4264,11 @@
Thread.new { @hd.handle_stream(stream: stream, file_type: file_type) }
@hd.wait_for_stream_processing
assert_equal [],
- @hd.instance_variable_get(:@run_state).files[ExecutionStreams::STD_OUT]
+ @hd.instance_variable_get(:@run_state).files.stream_lines(ExecutionStreams::STD_OUT)
end
end
class TestHashDelegatorIterBlocksFromNestedFiles < Minitest::Test
def setup
@@ -3995,13 +4282,13 @@
end
def test_iter_blocks_from_nested_files
@hd.cfile.expect(:readlines, ['line 1', 'line 2'], ['test.md'],
import_paths: nil)
- selected_messages = ['filtered message']
+ selected_types = ['filtered message']
- result = @hd.iter_blocks_from_nested_files { selected_messages }
+ result = @hd.iter_blocks_from_nested_files { selected_types }
assert_equal ['line 1', 'line 2'], result
@hd.cfile.verify
end
@@ -4022,15 +4309,15 @@
menu_chrome_color: :red,
menu_chrome_format: '-- %s --'
})
@hd.stubs(:menu_chrome_formatted_option).with(:menu_option_back_name).returns('-- Back --')
@hd.stubs(:string_send_color).with('-- Back --',
- :menu_chrome_color).returns('-- Back --'.red)
+ :menu_chrome_color).returns(AnsiString.new('-- Back --').red)
end
def test_menu_chrome_colored_option_with_color
- assert_equal '-- Back --'.red,
+ assert_equal AnsiString.new('-- Back --').red,
@hd.menu_chrome_colored_option(:menu_option_back_name)
end
def test_menu_chrome_colored_option_without_color
@hd.instance_variable_set(:@delegate_object,
@@ -4090,14 +4377,15 @@
@hd.instance_variable_set(:@delegate_object,
{ red: 'red', green: 'green' })
end
def test_string_send_color
- assert_equal 'Hello'.red, @hd.string_send_color('Hello', :red)
- assert_equal 'World'.green,
+ assert_equal AnsiString.new('Hello').red,
+ @hd.string_send_color('Hello', :red)
+ assert_equal AnsiString.new('World').green,
@hd.string_send_color('World', :green)
- assert_equal 'Default'.plain,
+ assert_equal AnsiString.new('Default').plain,
@hd.string_send_color('Default', :blue)
end
end
def test_yield_line_if_selected_with_line
@@ -4283,13 +4571,10 @@
assert_equal 'resolved_path_or_substituted_value', result
end
def test_prompt_for_filespec_with_interruption
$stdin = StringIO.new
- # rubocop disable:Lint/NestedMethodDefinition
def $stdin.gets; raise Interrupt; end
- # rubocop enable:Lint/NestedMethodDefinition
-
result = prompt_for_filespec_with_wildcard('*.txt')
assert_nil result
end
def test_prompt_for_filespec_with_empty_input