lib/hash_delegator.rb in markdown_exec-2.1.0 vs lib/hash_delegator.rb in markdown_exec-2.2.0
- old
+ new
@@ -29,16 +29,19 @@
require_relative 'filter'
require_relative 'fout'
require_relative 'hash'
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'
+$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.
def non_empty?
!empty?
@@ -52,11 +55,12 @@
# @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')
+ 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
@@ -76,21 +80,21 @@
# }
# 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.
+ # 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 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 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, key, value, default = nil)
- blocks.find { |item| item[key] == value } || default
+ def block_find(blocks, msg, value, default = nil)
+ blocks.find { |item| item.send(msg) == value } || default
end
def code_merge(*bodies)
merge_lists(*bodies)
end
@@ -130,12 +134,14 @@
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?
+ prev_item&.fetch(:chrome, nil) &&
+ !(prev_item && prev_item.oname.present?) &&
+ current_item&.fetch(:chrome, nil) &&
+ !(current_item && current_item.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.
@@ -186,17 +192,18 @@
# 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:, block_name: nil)
+ def next_link_state(block_name_from_cli:, was_using_cli:, block_state:,
+ block_name: nil)
# Set block_name based on block_name_from_cli
block_name = @cli_block_name if block_name_from_cli
# Determine the state of breaker based on was_using_cli and the block type
- # true only when block_name is nil, block_name_from_cli is false, was_using_cli is true, and the block_state.block[:shell] equals BlockType::BASH. In all other scenarios, breaker is false.
- breaker = !block_name && !block_name_from_cli && was_using_cli && block_state.block.fetch(:shell, nil) == BlockType::BASH
+ # true only when block_name is nil, block_name_from_cli is false, was_using_cli is true, and the block_state.block.shell equals BlockType::BASH. In all other scenarios, breaker is false.
+ breaker = !block_name && !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]
@@ -280,11 +287,12 @@
# 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:, messages:, configuration: {}, &block)
+ def update_menu_attrib_yield_selected(fcb:, 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, messages, configuration,
@@ -427,11 +435,13 @@
words = text.split
words.each.with_index do |word, index|
trial_length = word.length
trial_length += @first_indent.length if index.zero?
- trial_length += current_line.length + 1 + @rest_indent.length if index != 0
+ if index != 0
+ trial_length += current_line.length + 1 + @rest_indent.length
+ end
if trial_length > max_line_length && (words.count != 0)
lines << current_line
current_line = word
current_line = current_line.dup if current_line.frozen?
else
@@ -478,11 +488,12 @@
@prompt = HashDelegator.tty_prompt_without_disabled_symbol
@most_recent_loaded_filename = nil
@pass_args = []
@run_state = OpenStruct.new(
- link_history: []
+ link_history: [],
+ source: OpenStruct.new
)
@link_history = LinkHistory.new
@fout = FOut.new(@delegate_object) ### slice only relevant keys
@process_mutex = Mutex.new
@@ -504,17 +515,22 @@
#
# @param menu_blocks [Array] The array of menu block elements to be modified.
def add_menu_chrome_blocks!(menu_blocks:, link_state:)
return unless @delegate_object[:menu_link_format].present?
- add_inherited_lines(menu_blocks: menu_blocks, link_state: link_state) if @delegate_object[:menu_with_inherited_lines]
+ if @delegate_object[:menu_with_inherited_lines]
+ add_inherited_lines(menu_blocks: menu_blocks,
+ link_state: link_state)
+ end
# back before exit
add_back_option(menu_blocks: menu_blocks) if should_add_back_option?
# exit after other options
- add_exit_option(menu_blocks: menu_blocks) if @delegate_object[:menu_with_exit]
+ if @delegate_object[:menu_with_exit]
+ add_exit_option(menu_blocks: menu_blocks)
+ end
add_dividers(menu_blocks: menu_blocks)
end
private
@@ -586,22 +602,36 @@
if insert_at_top
menu_blocks.unshift(chrome_block)
else
menu_blocks.push(chrome_block)
end
+
+ chrome_block
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)
+ 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_inherited_lines(menu_blocks:, link_state:, position: top)
- return unless link_state.inherited_lines.present?
+ 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|
+ chrome_blocks = link_state.inherited_lines_map do |line|
formatted = format(@delegate_object[:menu_inherited_lines_format],
{ line: line })
FCB.new(
chrome: true,
disabled: '',
@@ -621,22 +651,10 @@
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)
- end
-
# private
# Applies shell color options to the given string if applicable.
#
# @param name [String] The name to potentially colorize.
@@ -670,11 +688,11 @@
blocks = []
iter_blocks_from_nested_files do |btype, fcb|
process_block_based_on_type(blocks, btype, fcb)
end
- # &bc 'blocks.count:', blocks.count
+ # &bt blocks.count
blocks
rescue StandardError
HashDelegator.error_handler('blocks_from_nested_files')
end
@@ -682,11 +700,12 @@
# if matched, the block returned has properties that it is from cli and not ui
def block_state_for_name_from_cli(block_name)
SelectedBlockMenuState.new(
@dml_blocks_in_file.find do |item|
block_name == item.pub_name
- end&.merge(
+ end,
+ OpenStruct.new(
block_name_from_cli: true,
block_name_from_ui: false
),
MenuState::CONTINUE
)
@@ -696,16 +715,18 @@
def calc_logged_stdout_filename(block_name:)
return unless @delegate_object[:saved_stdout_folder]
@delegate_object[:logged_stdout_filename] =
- SavedAsset.new(blockname: block_name,
- filename: @delegate_object[:filename],
- prefix: @delegate_object[:logged_stdout_filename_prefix],
- time: Time.now.utc,
- exts: '.out.txt',
- saved_asset_format: @delegate_object[:saved_asset_format]).generate_name
+ SavedAsset.new(
+ blockname: block_name,
+ filename: @delegate_object[:filename],
+ prefix: @delegate_object[:logged_stdout_filename_prefix],
+ time: Time.now.utc,
+ exts: '.out.txt',
+ saved_asset_format: shell_escape_asset_format(@dml_link_state)
+ ).generate_name
@logged_stdout_filespec =
@delegate_object[:logged_stdout_filespec] =
File.join @delegate_object[:saved_stdout_folder],
@delegate_object[:logged_stdout_filename]
@@ -735,17 +756,18 @@
# 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.
# @return [Array<String>] Required code blocks as an array of lines.
- def collect_required_code_lines(mdoc:, selected:, block_source:, link_state: LinkState.new)
+ def collect_required_code_lines(mdoc:, selected:, block_source:,
+ link_state: LinkState.new)
required = mdoc.collect_recursively_required_code(
anyname: selected.pub_name,
label_format_above: @delegate_object[:shell_code_label_format_above],
label_format_below: @delegate_object[:shell_code_label_format_below],
block_source: block_source
- )
+ ) # &bt 'required'
dependencies = (link_state&.inherited_dependencies || {}).merge(required[:dependencies] || {})
required[:unmet_dependencies] =
(required[:unmet_dependencies] || []) - (link_state&.inherited_block_names || [])
if required[:unmet_dependencies].present?
### filter against link_state.inherited_block_names
@@ -753,18 +775,23 @@
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])
- else
+ elsif false ### use option 2024-08-02
warn format_and_highlight_dependencies(dependencies,
highlight: [@delegate_object[:block_name]])
end
- code_lines = selected[:shell] == BlockType::VARS ? set_environment_variables_for_block(selected) : []
-
- HashDelegator.code_merge(link_state&.inherited_lines, required[:code] + code_lines)
+ if selected[:shell] == BlockType::OPTS
+ # body of blocks is returned as a list of lines to be read an YAML
+ HashDelegator.code_merge(required[:blocks].map(&:body).flatten(1))
+ else
+ code_lines = selected.shell == BlockType::VARS ? set_environment_variables_for_block(selected) : []
+ HashDelegator.code_merge(link_state&.inherited_lines,
+ required[:code] + code_lines)
+ end
end
def command_execute(command, args: [])
@run_state.files = StreamsOut.new
@run_state.options = @delegate_object
@@ -781,28 +808,31 @@
)
)
else
@run_state.in_own_window = false
execute_command_with_streams(
- [@delegate_object[:shell], '-c', command, @delegate_object[:filename], *args]
+ [@delegate_object[:shell], '-c', command,
+ @delegate_object[:filename], *args]
)
end
@run_state.completed_at = Time.now.utc
rescue Errno::ENOENT => err
# Handle ENOENT error
@run_state.aborted_at = Time.now.utc
@run_state.error_message = err.message
@run_state.error = err
- @run_state.files.append_stream_line(ExecutionStreams::STD_ERR, @run_state.error_message)
+ @run_state.files.append_stream_line(ExecutionStreams::STD_ERR,
+ @run_state.error_message)
@fout.fout "Error ENOENT: #{err.inspect}"
rescue SignalException => err
# Handle SignalException
@run_state.aborted_at = Time.now.utc
@run_state.error_message = 'SIGTERM'
@run_state.error = err
- @run_state.files.append_stream_line(ExecutionStreams::STD_ERR, @run_state.error_message)
+ @run_state.files.append_stream_line(ExecutionStreams::STD_ERR,
+ @run_state.error_message)
@fout.fout "Error ENOENT: #{err.inspect}"
end
def command_execute_in_own_window_format_arguments(home: Dir.pwd, rest: '')
{
@@ -828,22 +858,29 @@
# may display the code for user approval before execution. It then executes the approved block.
#
# @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)
+ 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)
output_or_approval = @delegate_object[:output_script] || @delegate_object[:user_must_approve]
- display_required_code(required_lines: required_lines) if output_or_approval
+ 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
- execute_required_lines(required_lines: required_lines, selected: selected) if allow_execution
+ 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
@@ -997,14 +1034,16 @@
# @return [Boolean] Execute the named block.
def debounce_allows
return true unless @delegate_object[:debounce_execution]
# filter block if selected in menu
- return true if @run_state.block_name_from_cli
+ return true if @run_state.source.block_name_from_cli
# return false if @prior_execution_block == @delegate_object[:block_name]
- return @allowed_execution_block == @prior_execution_block || prompt_approve_repeat if @prior_execution_block == @delegate_object[:block_name]
+ if @prior_execution_block == @delegate_object[:block_name]
+ return @allowed_execution_block == @prior_execution_block || prompt_approve_repeat
+ end
@prior_execution_block = @delegate_object[:block_name]
@allowed_execution_block = nil
true
end
@@ -1017,21 +1056,25 @@
# 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.
def determine_block_state(selected_option)
- option_name = selected_option.fetch(:oname, nil)
+ option_name = selected_option[:oname]
if option_name == menu_chrome_formatted_option(:menu_option_exit_name)
return SelectedBlockMenuState.new(nil,
+ OpenStruct.new,
MenuState::EXIT)
end
if option_name == menu_chrome_formatted_option(:menu_option_back_name)
return SelectedBlockMenuState.new(selected_option,
+ OpenStruct.new,
MenuState::BACK)
end
- SelectedBlockMenuState.new(selected_option, MenuState::CONTINUE)
+ SelectedBlockMenuState.new(selected_option,
+ OpenStruct.new,
+ MenuState::CONTINUE)
end
# Displays the required lines of code with color formatting for the preview section.
# It wraps the code lines between a formatted header and tail.
#
@@ -1066,32 +1109,42 @@
@menu_base_options = @delegate_object
@dml_link_state = LinkState.new(
block_name: @delegate_object[:block_name],
document_filename: @delegate_object[:filename]
)
- @run_state.block_name_from_cli = @dml_link_state.block_name.present?
+ @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.block_name_from_cli
+ @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 = []
## load file with code lines per options
#
if @menu_base_options[:load_code].present?
- @dml_link_state.inherited_lines = []
- @menu_base_options[:load_code].split(':').map do |path|
- @dml_link_state.inherited_lines += File.readlines(path, chomp: true)
- end
+ @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 = { oname: 'load_code' }
- pop_add_current_code_to_head_and_trigger_load(@dml_link_state, inherited_block_names, code_lines, inherited_dependencies, selected)
+ 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
- fdo = ->(mo) { format(@delegate_object[:menu_link_format], HashDelegator.safeval(@delegate_object[mo])) }
+ 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)
@@ -1099,40 +1152,69 @@
item_view = fdo.call(:menu_option_view_name)
@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.tap do |files|
- menu_enable_option(item_history, files.count, 'files', menu_state: MenuState::HISTORY) if files.count.positive?
+ 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])
+ 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 || 0
+ lines_count = @dml_link_state.inherited_lines_count
# add menu items (glob, load, save) and enable selectively
- menu_add_disabled_option(sf) if files.count.positive? || lines_count.positive?
- menu_enable_option(item_load, files.count, 'files', menu_state: MenuState::LOAD) if files.count.positive?
- menu_enable_option(item_edit, lines_count, 'lines', menu_state: MenuState::EDIT) if lines_count.positive?
- menu_enable_option(item_save, 1, '', menu_state: MenuState::SAVE) if lines_count.positive?
- menu_enable_option(item_view, 1, '', menu_state: MenuState::VIEW) if lines_count.positive?
- menu_enable_option(item_shell, 1, '', menu_state: MenuState::SHELL) if @delegate_object[:menu_with_shell]
+ 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
@@ -1141,11 +1223,11 @@
when :user_choice
if @dml_link_state.block_name.present?
# @prior_block_was_link = true
@dml_block_state.block = @dml_blocks_in_file.find do |item|
- item.pub_name == @dml_link_state.block_name
+ item.pub_name == @dml_link_state.block_name || item.oname == @dml_link_state.block_name
end
@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
@@ -1154,11 +1236,11 @@
# puts "! - Executing block: #{data}"
@dml_block_state.block&.pub_name
when :execute_block
case (block_name = data)
- when item_back
+ 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
@@ -1169,68 +1251,98 @@
document_filename: @dml_link_state.document_filename,
prior_block_was_link: true
)
)
- when item_edit
+ when item_edit.pub_name
debounce_reset
- edited = edit_text(@dml_link_state.inherited_lines.join("\n"))
+ 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
+ when item_history.pub_name
debounce_reset
- files = history_files
+ files = history_files(@dml_link_state)
files_table_rows = files.map do |file|
if Regexp.new(@delegate_object[:saved_asset_match]) =~ file
- OpenStruct.new(file: file, row: [$~[:time], $~[:blockname], $~[:exts]].join(' '))
+ 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
+ end&.compact
- case (name = prompt_select_code_filename(
- [@delegate_object[:prompt_filespec_back]] +
- files_table_rows.map(&:row),
- string: @delegate_object[:prompt_select_history_file],
- color_sym: :prompt_color_after_script_execution
- ))
- when @delegate_object[:prompt_filespec_back]
- # do nothing
- 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).violet, line)
- end)
+ 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
+ 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])
+ 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 ||= []
- @dml_link_state.inherited_lines += File.readlines(load_filespec, chomp: true)
+ @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
+ 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])
+ 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)
)
@@ -1238,11 +1350,11 @@
end
InputSequencer.next_link_state(prior_block_was_link: true)
- when item_shell
+ 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'
@@ -1259,57 +1371,57 @@
return :break if pause_user_exit
InputSequencer.next_link_state(prior_block_was_link: true)
- when item_view
+ when item_view.pub_name
debounce_reset
- warn @dml_link_state.inherited_lines.join("\n")
+ 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.fetch(:shell, nil) == BlockType::OPTS
+ 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(
- selected: @dml_block_state.block,
- link_state: link_state
+ link_state: link_state,
+ mdoc: @dml_mdoc,
+ selected: @dml_block_state.block
)
- @menu_base_options.merge!(options_state.options)
- @delegate_object.merge!(options_state.options)
+ 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.block_name_from_cli,
+ 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.block_name_from_cli, cli_break = \
+ @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.block[:block_name_from_ui] && cli_break
+ 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.fetch(:shell, nil) != BlockType::BASH
+ prior_block_was_link: @dml_block_state.block.shell != BlockType::BASH
)
end
end
when :exit?
@@ -1326,13 +1438,17 @@
end
# remove leading "./"
# replace characters: / : . * (space) with: (underscore)
def document_name_in_glob_as_file_name(document_filename, glob)
- return document_filename if document_filename.nil? || document_filename.empty?
+ if document_filename.nil? || document_filename.empty?
+ return document_filename
+ end
- format(glob, { document_filename: document_filename.gsub(%r{^\./}, '').gsub(/[\/:\.\* ]/, '_') })
+ format(glob,
+ { document_filename: document_filename.gsub(%r{^\./}, '').gsub(/[\/:\.\* ]/,
+ '_') })
end
def dump_and_warn_block_state(selected:)
if selected.nil?
Exceptions.warn_format("Block not found -- name: #{@delegate_object[:block_name]}",
@@ -1349,11 +1465,14 @@
# @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.
# @param link_state [LinkState] Current state of the link.
def dump_delobj(blocks_in_file, menu_blocks, link_state)
- warn format_and_highlight_hash(@delegate_object, label: '@delegate_object') if @delegate_object[:dump_delegate_object]
+ if @delegate_object[:dump_delegate_object]
+ warn format_and_highlight_hash(@delegate_object,
+ label: '@delegate_object')
+ end
if @delegate_object[:dump_blocks_in_file]
warn format_and_highlight_dependencies(compact_and_index_hash(blocks_in_file),
label: 'blocks_in_file')
end
@@ -1361,15 +1480,22 @@
if @delegate_object[:dump_menu_blocks]
warn format_and_highlight_dependencies(compact_and_index_hash(menu_blocks),
label: 'menu_blocks')
end
- warn format_and_highlight_lines(link_state.inherited_block_names, label: 'inherited_block_names') if @delegate_object[:dump_inherited_block_names]
- warn format_and_highlight_lines(link_state.inherited_dependencies, label: 'inherited_dependencies') if @delegate_object[:dump_inherited_dependencies]
+ if @delegate_object[:dump_inherited_block_names]
+ warn format_and_highlight_lines(link_state.inherited_block_names,
+ label: 'inherited_block_names')
+ end
+ if @delegate_object[:dump_inherited_dependencies]
+ warn format_and_highlight_lines(link_state.inherited_dependencies,
+ label: 'inherited_dependencies')
+ end
return unless @delegate_object[:dump_inherited_lines]
- warn format_and_highlight_lines(link_state.inherited_lines, label: 'inherited_lines')
+ warn format_and_highlight_lines(link_state.inherited_lines,
+ label: 'inherited_lines')
end
# Opens text in an editor for user modification and returns the modified text.
#
# This method reads the provided text, opens it in the default editor,
@@ -1430,11 +1556,11 @@
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]]
+ lfls.load_file == LoadFile::LOAD ? nil : selected.dname]
#.tap { |ret| pp [__FILE__,__LINE__,'exec_bash_next_state()',ret] }
end
# Executes a given command and processes its input, output, and error streams.
#
@@ -1451,21 +1577,24 @@
def execute_command_with_streams(command)
exit_status = nil
Open3.popen3(*command) do |stdin, stdout, stderr, exec_thread|
# Handle stdout stream
- handle_stream(stream: stdout, file_type: ExecutionStreams::STD_OUT) do |line|
+ handle_stream(stream: stdout,
+ file_type: ExecutionStreams::STD_OUT) do |line|
yield nil, line, nil, exec_thread if block_given?
end
# Handle stderr stream
- handle_stream(stream: stderr, file_type: ExecutionStreams::STD_ERR) do |line|
+ handle_stream(stream: stderr,
+ file_type: ExecutionStreams::STD_ERR) do |line|
yield nil, nil, line, exec_thread if block_given?
end
# Handle stdin stream
- input_thread = handle_stream(stream: $stdin, file_type: ExecutionStreams::STD_IN) do |line|
+ input_thread = handle_stream(stream: $stdin,
+ file_type: ExecutionStreams::STD_IN) do |line|
stdin.puts(line)
yield line, nil, nil, exec_thread if block_given?
end
# Wait for all streams to be processed
@@ -1488,12 +1617,17 @@
# 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: [], selected: FCB.new)
- write_command_file(required_lines: required_lines, selected: selected) if @delegate_object[:save_executed_script]
- calc_logged_stdout_filename(block_name: @dml_block_state.block[:oname]) if @dml_block_state
+ if @delegate_object[:save_executed_script]
+ write_command_file(required_lines: required_lines,
+ selected: selected)
+ end
+ if @dml_block_state
+ calc_logged_stdout_filename(block_name: @dml_block_state.block.oname)
+ end
format_and_execute_command(code_lines: required_lines)
post_execution_process
end
# Execute a code block after approval and provide user interaction options.
@@ -1503,64 +1637,53 @@
# 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)
- if selected.fetch(:shell, '') == BlockType::LINK
+ def execute_shell_type(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.fetch(:body, ''),
+ push_link_history_and_trigger_load(link_block_body: selected.body,
mdoc: mdoc,
selected: selected,
link_state: link_state,
block_source: block_source)
elsif @menu_user_clicked_back_link
debounce_reset
pop_link_history_and_trigger_load
- elsif selected[:shell] == BlockType::OPTS
+ elsif selected.shell == BlockType::OPTS
debounce_reset
- block_names = []
code_lines = []
- dependencies = {}
- options_state = read_show_options_and_trigger_reuse(selected: selected, link_state: link_state)
+ options_state = read_show_options_and_trigger_reuse(
+ link_state: link_state,
+ mdoc: @dml_mdoc,
+ selected: selected
+ )
+ update_menu_base(options_state.options)
- ## apply options to current state
- #
- @menu_base_options.merge!(options_state.options)
- @delegate_object.merge!(options_state.options)
-
### options_state.load_file_link_state
link_state = LinkState.new
- 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),
- next_block_name: '',
- next_document_filename: @delegate_object[:filename],
- next_load_file: LoadFile::REUSE
- )
+ next_state_append_code(selected, link_state, code_lines)
- elsif selected[:shell] == BlockType::VARS
+ elsif selected.shell == BlockType::PORT
debounce_reset
- block_names = []
- code_lines = set_environment_variables_for_block(selected)
- dependencies = {}
- 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),
- next_block_name: '',
- next_document_filename: @delegate_object[:filename],
- next_load_file: LoadFile::REUSE
+ required_lines = collect_required_code_lines(
+ mdoc: @dml_mdoc,
+ selected: selected,
+ link_state: link_state,
+ block_source: block_source
)
+ next_state_set_code(selected, link_state, required_lines)
+ elsif selected.shell == BlockType::VARS
+ debounce_reset
+ next_state_append_code(selected, link_state,
+ set_environment_variables_for_block(selected))
+
elsif debounce_allows
compile_execute_and_trigger_reuse(mdoc: mdoc,
selected: selected,
link_state: link_state,
block_source: block_source)
@@ -1644,10 +1767,20 @@
# Expand expression if it contains format specifiers
def formatted_expression(expr)
expr.include?('%{') ? format_expression(expr) : expr
end
+ def generate_temp_filename(ext = '.sh')
+ filename = begin
+ Dir::Tmpname.make_tmpname(['x', ext], nil)
+ rescue NoMethodError
+ require 'securerandom'
+ "#{SecureRandom.urlsafe_base64}#{ext}"
+ end
+ File.join(Dir.tmpdir, filename)
+ 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.
@@ -1657,24 +1790,25 @@
fcb.call = fcb.title.match(Regexp.new(@delegate_object[:block_calls_scan]))&.fetch(1, nil)
titlexcall = fcb.call ? fcb.title.sub("%#{fcb.call}", '') : fcb.title
bm = extract_named_captures_from_option(titlexcall,
@delegate_object[:block_name_match])
- shell_color_option = SHELL_COLOR_OPTIONS[fcb[:shell]]
+ shell_color_option = SHELL_COLOR_OPTIONS[fcb.shell]
if @delegate_object[:block_name_nick_match].present? && fcb.oname =~ Regexp.new(@delegate_object[:block_name_nick_match])
fcb.nickname = $~[0]
fcb.title = fcb.oname = format_multiline_body_as_title(fcb.body)
else
fcb.title = fcb.oname = bm && bm[1] ? bm[:title] : titlexcall
end
fcb.dname = HashDelegator.indent_all_lines(
apply_shell_color_option(fcb.oname, shell_color_option),
- fcb.fetch(:indent, nil)
+ fcb.indent
)
- fcb
+
+ fcb # &br
end
# 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.
#
@@ -1693,56 +1827,71 @@
def handle_stream(stream:, file_type:, swap: false)
@process_mutex.synchronize do
Thread.new do
stream.each_line do |line|
line.strip!
- @run_state.files.append_stream_line(file_type, line) if @run_state.files.streams
-
- if @delegate_object[:output_stdout]
- # print line
- puts line
+ if @run_state.files.streams
+ @run_state.files.append_stream_line(file_type,
+ line)
end
+ puts line if @delegate_object[:output_stdout]
+
yield line if block_given?
end
rescue IOError
# Handle IOError
ensure
@process_cv.signal
end
end
end
- def history_files
- Dir.glob(
+ def history_files(link_state, order: :chronological, direction: :reverse)
+ files = Dir.glob(
File.join(
@delegate_object[:saved_script_folder],
- SavedAsset.new(filename: @delegate_object[:filename],
- saved_asset_format: @delegate_object[:saved_asset_format]).generate_name
+ SavedAsset.new(
+ filename: @delegate_object[:filename],
+ saved_asset_format: shell_escape_asset_format(link_state)
+ ).generate_name
)
)
+
+ sorted_files = case order
+ when :alphabetical
+ files.sort
+ when :chronological
+ files.sort_by { |file| File.mtime(file) }
+ else
+ raise ArgumentError, "Invalid order: #{order}"
+ end
+
+ direction == :reverse ? sorted_files.reverse : sorted_files
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,}'
- )),
- fenced_start_extended_regex: Regexp.new(@delegate_object.fetch(
- :fenced_start_and_end_regex, '^(?<indent> *)`{3,}'
- )),
+ fenced_start_and_end_regex:
+ Regexp.new(@delegate_object.fetch(
+ :fenced_start_and_end_regex, '^(?<indent> *)`{3,}'
+ )),
+ fenced_start_extended_regex:
+ Regexp.new(@delegate_object.fetch(
+ :fenced_start_and_end_regex, '^(?<indent> *)`{3,}'
+ )),
fcb: MarkdownExec::FCB.new,
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 = \
+ @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: {
@@ -1755,21 +1904,21 @@
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.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.block_name_from_cli,
+ @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.block_name_from_cli:',@run_state.block_name_from_cli
+ # &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
@@ -1791,12 +1940,14 @@
&block)
end
end
end
- def link_block_data_eval(link_state, code_lines, selected, link_block_data, block_source:)
- all_code = HashDelegator.code_merge(link_state&.inherited_lines, code_lines)
+ def link_block_data_eval(link_state, code_lines, selected, link_block_data,
+ block_source:)
+ all_code = HashDelegator.code_merge(link_state&.inherited_lines,
+ code_lines)
output_lines = []
Tempfile.open do |file|
cmd = "#{@delegate_object[:shell]} #{file.path}"
file.write(all_code.join("\n"))
@@ -1811,31 +1962,39 @@
## select output_lines that look like assignment or match other specs
#
output_lines = process_string_array(
output_lines,
- begin_pattern: @delegate_object.fetch(:output_assignment_begin, nil),
+ begin_pattern: @delegate_object.fetch(:output_assignment_begin,
+ nil),
end_pattern: @delegate_object.fetch(:output_assignment_end, nil),
scan1: @delegate_object.fetch(:output_assignment_match, nil),
format1: @delegate_object.fetch(:output_assignment_format, nil)
)
else
output_lines = `#{cmd}`.split("\n")
end
end
- HashDelegator.error_handler('all_code eval output_lines is nil', { abort: true }) unless output_lines
+ unless output_lines
+ HashDelegator.error_handler('all_code eval output_lines is nil',
+ { abort: true })
+ 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 }))] +
output_lines.map do |line|
re = Regexp.new(link_block_data.fetch('pattern', '(?<line>.*)'))
- re.gsub_format(line, link_block_data.fetch('format', '%{line}')) if re =~ 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 }))]
end
@@ -1880,38 +2039,45 @@
# Loads auto blocks based on delegate object settings and updates if new filename is detected.
# Executes a specified block once per filename.
# @param all_blocks [Array] Array of all block elements.
# @return [Boolean, nil] True if values were modified, nil otherwise.
- def load_auto_opts_block(all_blocks)
+ def load_auto_opts_block(all_blocks, mdoc:)
block_name = @delegate_object[:document_load_opts_block_name]
- return unless block_name.present? && @most_recent_loaded_filename != @delegate_object[:filename]
+ unless block_name.present? && @most_recent_loaded_filename != @delegate_object[:filename]
+ return
+ end
block = HashDelegator.block_find(all_blocks, :oname, block_name)
return unless block
- options_state = read_show_options_and_trigger_reuse(selected: block)
- @menu_base_options.merge!(options_state.options)
- @delegate_object.merge!(options_state.options)
+ options_state = read_show_options_and_trigger_reuse(
+ mdoc: mdoc,
+ selected: block
+ )
+ update_menu_base(options_state.options)
@most_recent_loaded_filename = @delegate_object[:filename]
true
end
- def load_cli_or_user_selected_block(all_blocks: [], menu_blocks: [], default: nil)
+ def load_cli_or_user_selected_block(all_blocks: [], menu_blocks: [],
+ default: nil)
if @delegate_object[:block_name].present?
block = all_blocks.find do |item|
item.pub_name == @delegate_object[:block_name]
- end&.merge(block_name_from_ui: false)
+ end
+ source = OpenStruct.new(block_name_from_ui: false)
else
block_state = wait_for_user_selected_block(all_blocks, menu_blocks,
default)
- block = block_state.block&.merge(block_name_from_ui: true)
+ block = block_state.block
+ source = OpenStruct.new(block_name_from_ui: true)
state = block_state.state
end
- SelectedBlockMenuState.new(block, state)
+ 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
@@ -1930,11 +2096,12 @@
# Handle expression with wildcard characters
def load_filespec_wildcard_expansion(expr, auto_load_single: false)
files = find_files(expr)
if files.count.zero?
- HashDelegator.error_handler("no files found with '#{expr}' ", { abort: true })
+ HashDelegator.error_handler("no files found with '#{expr}' ",
+ { abort: true })
elsif auto_load_single && files.count == 1
files.first
else
## user selects from existing files or other
#
@@ -1964,24 +2131,26 @@
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_opts_block(all_blocks)
+ all_blocks, mdoc = mdoc_and_blocks_from_nested_files if load_auto_opts_block(
+ all_blocks, mdoc: mdoc
+ )
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)
- [all_blocks, menu_blocks, mdoc]
+ [all_blocks, menu_blocks, mdoc] # &br
end
def menu_add_disabled_option(name)
raise unless name.present?
raise if @dml_menu_blocks.nil?
- block = @dml_menu_blocks.find { |item| item[:oname] == name }
+ block = @dml_menu_blocks.find { |item| item.oname == name }
# create menu item when it is needed (count > 0)
#
return unless block.nil?
@@ -2015,11 +2184,13 @@
# 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 = HashDelegator.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
@@ -2028,39 +2199,40 @@
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 }
+ item = @dml_menu_blocks.find { |block| block.oname == name }
# create menu item when it is needed (count > 0)
#
if item.nil? && count.positive?
- append_chrome_block(menu_blocks: @dml_menu_blocks, menu_state: menu_state)
- item = @dml_menu_blocks.find { |block| block[:oname] == name }
+ 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
+ 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:)
+ 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] = \
+ @menu_base_options[:block_name] =
@delegate_object[:block_name] = \
- link_state.block_name = \
+ link_state.block_name =
@cli_block_name = nil
end
@delegate_object = @menu_base_options.dup
@menu_user_clicked_back_link = false
@@ -2077,10 +2249,31 @@
@delegate_object[method_name]
# super
end
end
+ def next_state_append_code(selected, link_state, code_lines)
+ next_state_set_code(selected, link_state, HashDelegator.code_merge(
+ link_state&.inherited_lines, code_lines
+ ))
+ end
+
+ def next_state_set_code(selected, link_state, code_lines)
+ block_names = []
+ dependencies = {}
+ 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(code_lines),
+ next_block_name: '',
+ next_document_filename: @delegate_object[:filename],
+ 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
@@ -2157,13 +2350,16 @@
else
# no history exists; must have been called independently => retain script
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),
+ 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),
next_block_name: next_block_name,
next_document_filename: @delegate_object[:filename], # not next_document_filename
next_load_file: LoadFile::REUSE # not next_document_filename == @delegate_object[:filename] ? LoadFile::REUSE : LoadFile::LOAD
)
# LoadFileLinkState.new(LoadFile::REUSE, link_state)
@@ -2175,16 +2371,19 @@
#
# @return [LoadFileLinkState] An object indicating the action to load the next block.
def pop_link_history_and_trigger_load
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
- ))
+ 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
+ )
+ )
end
def post_execution_process
do_save_execution_output
output_execution_summary
@@ -2196,24 +2395,25 @@
# @param all_blocks [Array<Hash>] The list of blocks from the file.
# @param opts [Hash] The options hash.
# @return [Array<Hash>] The updated blocks menu.
def prepare_blocks_menu(menu_blocks)
menu_blocks.map do |fcb|
- next if Filter.prepared_not_in_menu?(@delegate_object, fcb,
- %i[block_name_include_match block_name_wrapper_match])
+ next if Filter.prepared_not_in_menu?(
+ @delegate_object,
+ fcb,
+ %i[block_name_include_match block_name_wrapper_match]
+ )
- fcb.merge!(
- name: fcb.dname,
- label: BlockLabel.make(
- body: fcb[:body],
- filename: @delegate_object[:filename],
- headings: fcb.fetch(:headings, []),
- menu_blocks_with_docname: @delegate_object[:menu_blocks_with_docname],
- menu_blocks_with_headings: @delegate_object[:menu_blocks_with_headings],
- text: fcb[:text],
- title: fcb[:title]
- )
+ fcb.name = fcb.dname
+ fcb.label = BlockLabel.make(
+ body: fcb.body,
+ filename: @delegate_object[:filename],
+ headings: fcb.headings,
+ menu_blocks_with_docname: @delegate_object[:menu_blocks_with_docname],
+ menu_blocks_with_headings: @delegate_object[:menu_blocks_with_headings],
+ text: fcb.text,
+ title: fcb.title
)
fcb.to_h
end.compact
end
@@ -2230,11 +2430,14 @@
when :blocks
blocks.push(get_block_summary(fcb))
when :filter
%i[blocks line]
when :line
- create_and_add_chrome_blocks(blocks, fcb) unless @delegate_object[:no_chrome]
+ unless @delegate_object[:no_chrome]
+ create_and_add_chrome_blocks(blocks,
+ fcb)
+ end
end
end
def process_string_array(arr, begin_pattern: nil, end_pattern: nil, scan1: nil,
format1: nil)
@@ -2303,11 +2506,12 @@
# If interrupted by the user (e.g., pressing Ctrl-C), it returns nil.
#
# @param filespec [String] the wildcard expression to be substituted
# @return [String, nil] the resolved path or substituted expression, or nil if interrupted
def prompt_for_filespec_with_wildcard(filespec)
- puts format(@delegate_object[:prompt_show_expr_format], { expr: filespec })
+ puts format(@delegate_object[:prompt_show_expr_format],
+ { expr: filespec })
puts @delegate_object[:prompt_enter_filespec]
begin
input = gets.chomp
PathUtils.resolve_path_or_substitute(input, filespec)
@@ -2360,30 +2564,44 @@
exit 1
end
# public
- def prompt_select_code_filename(filenames, string: @delegate_object[:prompt_select_code_file], color_sym: :prompt_color_after_script_execution)
+ def prompt_select_code_filename(
+ filenames,
+ color_sym: :prompt_color_after_script_execution,
+ cycle: true,
+ enum: false,
+ quiet: true,
+ string: @delegate_object[:prompt_select_code_file]
+ )
@prompt.select(
string_send_color(string, color_sym),
- filter: true,
- quiet: true
+ cycle: cycle,
+ filter: !enum,
+ per_page: @delegate_object[:select_page_height],
+ quiet: quiet
) do |menu|
- filenames.each do |filename|
- menu.choice filename
+ menu.enum '.' if enum
+ filenames.each.with_index do |filename, ind|
+ if enum
+ menu.choice filename, ind + 1
+ else
+ menu.choice filename
+ end
end
end
rescue TTY::Reader::InputInterrupt
exit 1
end
- def prompt_select_continue
+ def prompt_select_continue(filter: true, quiet: true)
sel = @prompt.select(
string_send_color(@delegate_object[:prompt_after_script_execution],
:prompt_color_after_script_execution),
- filter: true,
- quiet: true
+ filter: filter,
+ quiet: quiet
) do |menu|
menu.choice @delegate_object[:prompt_yes]
menu.choice @delegate_object[:prompt_exit]
end
sel == @delegate_object[:prompt_exit] ? MenuState::EXIT : MenuState::CONTINUE
@@ -2392,11 +2610,11 @@
end
# user prompt to exit if the menu will be displayed again
#
def prompt_user_exit(block_name_from_cli:, selected:)
- selected[:shell] == BlockType::BASH &&
+ selected.shell == BlockType::BASH &&
@delegate_object[:pause_after_script_execution] &&
prompt_select_continue == MenuState::EXIT
end
# Handles the processing of a link block in Markdown Execution.
@@ -2441,22 +2659,30 @@
## append blocks loaded
#
if (load_expr = link_block_data.fetch(LinkKeys::LOAD, '')).present?
load_filespec = load_filespec_from_expression(load_expr)
- code_lines += File.readlines(load_filespec, chomp: true) if load_filespec
+ if load_filespec
+ code_lines += File.readlines(load_filespec,
+ chomp: true)
+ end
end
# if an eval link block, evaluate code_lines and return its standard output
#
if link_block_data.fetch(LinkKeys::EVAL,
- false) || link_block_data.fetch(LinkKeys::EXEC, false)
- code_lines = link_block_data_eval(link_state, code_lines, selected, link_block_data, block_source: block_source)
+ false) || link_block_data.fetch(LinkKeys::EXEC,
+ false)
+ code_lines = link_block_data_eval(link_state, code_lines, selected, link_block_data,
+ block_source: block_source)
end
- next_document_filename = write_inherited_lines_to_file(link_state, link_block_data)
- next_block_name = link_block_data.fetch(LinkKeys::NEXT_BLOCK, nil) || link_block_data.fetch(LinkKeys::BLOCK, nil) || ''
+ next_document_filename = write_inherited_lines_to_file(link_state,
+ link_block_data)
+ next_block_name = link_block_data.fetch(LinkKeys::NEXT_BLOCK,
+ nil) || link_block_data.fetch(LinkKeys::BLOCK,
+ nil) || ''
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)
@@ -2464,11 +2690,13 @@
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),
+ inherited_lines: HashDelegator.code_merge(
+ link_state&.inherited_lines, code_lines
+ ),
next_block_name: next_block_name,
next_document_filename: next_document_filename,
next_load_file: next_document_filename == @delegate_object[:filename] ? LoadFile::REUSE : LoadFile::LOAD
)
end
@@ -2485,18 +2713,33 @@
# 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)
+ def read_show_options_and_trigger_reuse(selected:,
+ mdoc:, link_state: LinkState.new)
obj = {}
- data = YAML.load(selected[:body].join("\n"))
- (data || []).each do |key, value|
- sym_key = key.to_sym
- obj[sym_key] = value
- print_formatted_option(key, value) if @delegate_object[:menu_opts_set_format].present?
+ # concatenated body of all required blocks loaded a YAML
+ data = YAML.load(
+ collect_required_code_lines(
+ mdoc: mdoc, selected: selected,
+ link_state: link_state, block_source: {}
+ ).join("\n")
+ ).transform_keys(&:to_sym)
+
+ if selected.shell == BlockType::OPTS
+ obj = data
+ else
+ (data || []).each do |key, value|
+ sym_key = key.to_sym
+ obj[sym_key] = value
+
+ if @delegate_object[:menu_opts_set_format].present?
+ print_formatted_option(key, value)
+ end
+ end
end
link_state.block_name = nil
OpenStruct.new(options: obj,
load_file_link_state: LoadFileLinkState.new(
@@ -2524,15 +2767,23 @@
def register_console_attributes(opts)
if (resized = @delegate_object[:menu_resize_terminal])
resize_terminal
end
- opts[:console_height], opts[:console_width] = opts[:console_winsize] = IO.console.winsize if resized || !opts[:console_width]
+ if resized || !opts[:console_width]
+ opts[:console_height], opts[:console_width] = opts[:console_winsize] =
+ IO.console.winsize
+ end
- opts[:per_page] = opts[:select_page_height] = [opts[:console_height] - 3, 4].max unless opts[:select_page_height]&.positive?
+ unless opts[:select_page_height]&.positive?
+ opts[:per_page] =
+ opts[:select_page_height] =
+ [opts[:console_height] - 3, 4].max
+ end
rescue StandardError
- HashDelegator.error_handler('register_console_attributes', { abort: true })
+ HashDelegator.error_handler('register_console_attributes',
+ { abort: true })
end
# Check if the delegate object responds to a given method.
# @param method_name [Symbol] The name of the method to check.
# @param include_private [Boolean] Whether to include private methods in the check.
@@ -2540,11 +2791,12 @@
def respond_to?(method_name, include_private = false)
if super
true
elsif @delegate_object.respond_to?(method_name, include_private)
true
- elsif method_name.to_s.end_with?('=') && @delegate_object.respond_to?(:[]=, include_private)
+ elsif method_name.to_s.end_with?('=') && @delegate_object.respond_to?(:[]=,
+ include_private)
true
else
@delegate_object.respond_to?(method_name, include_private)
end
end
@@ -2591,11 +2843,12 @@
else
## user selects from existing files or other
# input into path with wildcard for easy entry
#
case (name = prompt_select_code_filename(
- [@delegate_object[:prompt_filespec_back], @delegate_object[:prompt_filespec_other]] + files,
+ [@delegate_object[:prompt_filespec_back],
+ @delegate_object[:prompt_filespec_other]] + files,
string: @delegate_object[:prompt_select_code_file],
color_sym: :prompt_color_after_script_execution
))
when @delegate_object[:prompt_filespec_back]
# do nothing
@@ -2611,41 +2864,50 @@
write_command_file(required_lines: required_lines, selected: selected)
@fout.fout "File saved: #{@run_state.saved_filespec}"
end
# Presents a TTY prompt to select an option or exit, returns metadata including option and selected
- def select_option_with_metadata(prompt_text, names, opts = {})
+ def select_option_with_metadata(prompt_text, menu_items, opts = {})
## configure to environment
#
register_console_attributes(opts)
# crashes if all menu options are disabled
selection = @prompt.select(prompt_text,
- names,
+ menu_items,
opts.merge(filter: true))
- selected_name = names.find do |item|
+
+ selected = menu_items.find do |item|
if item.instance_of?(Hash)
- item[:dname] == selection
+ (item[:name] || item[:dname]) == selection
+ elsif item.instance_of?(MarkdownExec::FCB)
+ item.dname == selection
else
item == selection
end
end
- selected_name = { dname: selected_name } if selected_name.instance_of?(String)
- unless selected_name
- HashDelegator.error_handler('select_option_with_metadata', error: 'menu item not found')
+ if selected.instance_of?(String)
+ selected = FCB.new(dname: selected)
+ elsif selected.instance_of?(Hash)
+ selected = FCB.new(selected)
+ end
+ unless selected
+ HashDelegator.error_handler('select_option_with_metadata',
+ error: 'menu item not found')
exit 1
end
- selected_name.merge(
- if selection == menu_chrome_colored_option(:menu_option_back_name)
- { option: selection, shell: BlockType::LINK }
- elsif selection == menu_chrome_colored_option(:menu_option_exit_name)
- { option: selection }
- else
- { selected: selection }
- end
- )
+ if selection == menu_chrome_colored_option(:menu_option_back_name)
+ selected.option = selection
+ selected.shell = BlockType::LINK
+ elsif selection == menu_chrome_colored_option(:menu_option_exit_name)
+ selected.option = selection
+ else
+ selected.selected = selection
+ end
+
+ selected
rescue TTY::Reader::InputInterrupt
exit 1
rescue StandardError
HashDelegator.error_handler('select_option_with_metadata')
end
@@ -2661,12 +2923,13 @@
@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 = \
+ 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)
@@ -2679,11 +2942,11 @@
[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|
+ YAML.load(selected.body.join("\n"))&.each do |key, value|
ENV[key] = value.to_s
require 'shellwords'
code_lines.push "#{key}=\"#{Shellwords.escape(value)}\""
@@ -2694,10 +2957,33 @@
print string_send_color(formatted_string, :menu_vars_set_color)
end
code_lines
end
+ def shell_escape_asset_format(link_state)
+ raw = @delegate_object[:saved_asset_format]
+
+ return raw unless @delegate_object[:shell_parameter_expansion]
+
+ # unchanged if no parameter expansion takes place
+ return raw unless /$/ =~ raw
+
+ filespec = generate_temp_filename
+ cmd = [@delegate_object[:shell], '-c', filespec].join(' ')
+
+ marker = Random.new.rand.to_s
+
+ code = (link_state&.inherited_lines || []) + ["echo -n \"#{marker}#{raw}\""]
+ # &bt code
+ File.write filespec, HashDelegator.join_code_lines(code)
+ File.chmod 0o755, filespec
+
+ out = `#{cmd}`.sub(/.*?#{marker}/m, '')
+ File.delete filespec
+ out # &br
+ end
+
def should_add_back_option?
@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.
@@ -2816,10 +3102,17 @@
# &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)
+ @delegate_object.merge!(options)
+ end
+
def wait_for_stream_processing
@process_mutex.synchronize do
@process_cv.wait(@process_mutex)
end
rescue Interrupt
@@ -2837,12 +3130,15 @@
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
)
- block_menu = prepare_blocks_menu(menu_blocks)
- return SelectedBlockMenuState.new(nil, MenuState::EXIT) if block_menu.empty?
+ menu_items = prepare_blocks_menu(menu_blocks)
+ if menu_items.empty?
+ return SelectedBlockMenuState.new(nil, OpenStruct.new,
+ MenuState::EXIT)
+ end
# default value may not match if color is different from originating menu (opts changed while processing)
selection_opts = if default && menu_blocks.map(&:dname).include?(default)
@delegate_object.merge(default: default)
else
@@ -2850,11 +3146,11 @@
end
sph = @delegate_object[:select_page_height]
selection_opts.merge!(per_page: sph)
- selected_option = select_option_with_metadata(prompt_title, block_menu,
+ selected_option = select_option_with_metadata(prompt_title, menu_items,
selection_opts)
determine_block_state(selected_option)
end
# Handles the core logic for generating the command file's metadata and content.
@@ -2865,11 +3161,11 @@
@run_state.saved_script_filename =
SavedAsset.new(blockname: selected.pub_name,
exts: '.sh',
filename: @delegate_object[:filename],
prefix: @delegate_object[:saved_script_filename_prefix],
- saved_asset_format: @delegate_object[:saved_asset_format],
+ saved_asset_format: shell_escape_asset_format(@dml_link_state),
time: time_now).generate_name
@run_state.saved_filespec =
File.join(@delegate_object[:saved_script_folder],
@run_state.saved_script_filename)
@@ -2916,11 +3212,12 @@
# return next document file name
def write_inherited_lines_to_file(link_state, link_block_data)
save_expr = link_block_data.fetch(LinkKeys::SAVE, '')
if save_expr.present?
save_filespec = save_filespec_from_expression(save_expr)
- File.write(save_filespec, HashDelegator.join_code_lines(link_state&.inherited_lines))
+ File.write(save_filespec,
+ HashDelegator.join_code_lines(link_state&.inherited_lines))
@delegate_object[:filename]
else
link_block_data[LinkKeys::FILE] || @delegate_object[:filename]
end
end
@@ -2948,11 +3245,15 @@
obj.each do |key, value|
cleaned_value = clean_value(value) # Clean and possibly convert value
obj[key] = cleaned_value if value.is_a?(Hash) || value.is_a?(Struct)
end
- obj.reject! { |_key, value| [nil, '', [], {}, nil].include?(value) } if obj.is_a?(Hash)
+ if obj.is_a?(Hash)
+ obj.reject! do |_key, value|
+ [nil, '', [], {}, nil].include?(value)
+ end
+ end
obj
end
def self.next_link_state(*args, **kwargs, &block)
@@ -3012,11 +3313,12 @@
@hd = HashDelegator.new
end
# Test case for empty body
def test_next_link_state
- @hd.next_link_state(block_name_from_cli: nil, was_using_cli: nil, block_state: nil, block_name: nil)
+ @hd.next_link_state(block_name_from_cli: nil, was_using_cli: nil, block_state: nil,
+ block_name: nil)
end
end
class TestHashDelegator < Minitest::Test
def setup
@@ -3059,19 +3361,22 @@
end
# Test case for non-empty body with 'file' key
def test_push_link_history_and_trigger_load_with_file_key
body = ["file: sample_file\nblock: sample_block\nvars:\n KEY: VALUE"]
- expected_result = LoadFileLinkState.new(LoadFile::LOAD,
- LinkState.new(block_name: 'sample_block',
- document_filename: 'sample_file',
- inherited_dependencies: {},
- inherited_lines: ['# ', 'KEY="VALUE"']))
+ expected_result = LoadFileLinkState.new(
+ LoadFile::LOAD,
+ LinkState.new(block_name: 'sample_block',
+ document_filename: 'sample_file',
+ inherited_dependencies: {},
+ inherited_lines: ['# ', 'KEY="VALUE"'])
+ )
assert_equal expected_result,
@hd.push_link_history_and_trigger_load(
link_block_body: body,
- selected: FCB.new(block_name: 'sample_block', filename: 'sample_file')
+ selected: FCB.new(block_name: 'sample_block',
+ filename: 'sample_file')
)
end
def test_indent_all_lines_with_indent
body = "Line 1\nLine 2"
@@ -3176,24 +3481,24 @@
def setup
@hd = HashDelegator.new
end
def test_block_find_with_match
- blocks = [{ key: 'value1' }, { key: 'value2' }]
- result = HashDelegator.block_find(blocks, :key, 'value1')
- assert_equal({ key: 'value1' }, result)
+ blocks = [FCB.new(text: 'value1'), FCB.new(text: 'value2')]
+ result = HashDelegator.block_find(blocks, :text, 'value1')
+ assert_equal('value1', result.text)
end
def test_block_find_without_match
- blocks = [{ key: 'value1' }, { key: 'value2' }]
- result = HashDelegator.block_find(blocks, :key, 'value3')
+ blocks = [FCB.new(text: 'value1'), FCB.new(text: 'value2')]
+ result = HashDelegator.block_find(blocks, :text, 'missing_value')
assert_nil result
end
def test_block_find_with_default
- blocks = [{ key: 'value1' }, { key: 'value2' }]
- result = HashDelegator.block_find(blocks, :key, 'value3', 'default')
+ blocks = [FCB.new(text: 'value1'), FCB.new(text: 'value2')]
+ result = HashDelegator.block_find(blocks, :text, 'missing_value', 'default')
assert_equal 'default', result
end
end
class TestHashDelegatorBlocksFromNestedFiles < Minitest::Test
@@ -3225,20 +3530,21 @@
class TestHashDelegatorCollectRequiredCodeLines < Minitest::Test
def setup
@hd = HashDelegator.new
@hd.instance_variable_set(:@delegate_object, {})
@mdoc = mock('YourMDocClass')
- @selected = { shell: BlockType::VARS, body: ['key: value'] }
+ @selected = FCB.new(shell: BlockType::VARS, body: ['key: value'])
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
YAML.stubs(:load).returns({ 'key' => 'value' })
@mdoc.stubs(:collect_recursively_required_code).returns({ code: ['code line'] })
- result = @hd.collect_required_code_lines(mdoc: @mdoc, selected: @selected, block_source: {})
+ result = @hd.collect_required_code_lines(mdoc: @mdoc, selected: @selected,
+ block_source: {})
assert_equal ['code line', 'key="value"'], result
end
end
@@ -3255,22 +3561,28 @@
@hd.instance_variable_set(:@delegate_object,
{ block_name: 'block1' })
result = @hd.load_cli_or_user_selected_block(all_blocks: all_blocks)
- assert_equal all_blocks.first.merge(block_name_from_ui: false), result.block
+ assert_equal all_blocks.first,
+ result.block
+ assert_equal OpenStruct.new(block_name_from_ui: false),
+ result.source
assert_nil result.state
end
def test_user_selected_block
- block_state = SelectedBlockMenuState.new({ oname: 'block2' },
+ block_state = SelectedBlockMenuState.new({ oname: 'block2' }, OpenStruct.new,
:some_state)
@hd.stubs(:wait_for_user_selected_block).returns(block_state)
result = @hd.load_cli_or_user_selected_block
- assert_equal block_state.block.merge(block_name_from_ui: true), result.block
+ assert_equal block_state.block,
+ result.block
+ assert_equal OpenStruct.new(block_name_from_ui: true),
+ result.source
assert_equal :some_state, result.state
end
end
class TestHashDelegatorCountBlockInFilename < Minitest::Test
@@ -3349,30 +3661,30 @@
@hd = HashDelegator.new
@hd.stubs(:menu_chrome_formatted_option).returns('Formatted Option')
end
def test_determine_block_state_exit
- selected_option = { oname: 'Formatted Option' }
+ selected_option = FCB.new(oname: 'Formatted Option')
@hd.stubs(:menu_chrome_formatted_option).with(:menu_option_exit_name).returns('Formatted Option')
result = @hd.determine_block_state(selected_option)
assert_equal MenuState::EXIT, result.state
assert_nil result.block
end
def test_determine_block_state_back
- selected_option = { oname: 'Formatted Back Option' }
+ selected_option = FCB.new(oname: 'Formatted Back Option')
@hd.stubs(:menu_chrome_formatted_option).with(:menu_option_back_name).returns('Formatted Back Option')
result = @hd.determine_block_state(selected_option)
assert_equal MenuState::BACK, result.state
assert_equal selected_option, result.block
end
def test_determine_block_state_continue
- selected_option = { oname: 'Other Option' }
+ selected_option = FCB.new(oname: 'Other Option')
result = @hd.determine_block_state(selected_option)
assert_equal MenuState::CONTINUE, result.state
assert_equal selected_option, result.block
@@ -3478,11 +3790,12 @@
end
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
def test_format_execution_stream_with_nil_files
@@ -3637,11 +3950,12 @@
@hd.stubs(:cfile).returns(Minitest::Mock.new)
@hd.stubs(:update_line_and_block_state)
end
def test_iter_blocks_from_nested_files
- @hd.cfile.expect(:readlines, ['line 1', 'line 2'], ['test.md'], import_paths: nil)
+ @hd.cfile.expect(:readlines, ['line 1', 'line 2'], ['test.md'],
+ import_paths: nil)
selected_messages = ['filtered message']
result = @hd.iter_blocks_from_nested_files { selected_messages }
assert_equal ['line 1', 'line 2'], result
@@ -3743,11 +4057,12 @@
end
end
def test_yield_line_if_selected_with_line
block_called = false
- HashDelegator.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
@@ -3778,19 +4093,22 @@
end
def test_update_menu_attrib_yield_selected_with_body
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], {})
+ Filter.expects(:yield_to_block_if_applicable).with(@fcb, [:some_message],
+ {})
- HashDelegator.update_menu_attrib_yield_selected(fcb: @fcb, messages: [:some_message])
+ HashDelegator.update_menu_attrib_yield_selected(fcb: @fcb,
+ messages: [:some_message])
end
def test_update_menu_attrib_yield_selected_without_body
@fcb.stubs(:body).returns(nil)
HashDelegator.expects(:initialize_fcb_names).with(@fcb)
- HashDelegator.update_menu_attrib_yield_selected(fcb: @fcb, messages: [:some_message])
+ HashDelegator.update_menu_attrib_yield_selected(fcb: @fcb,
+ messages: [:some_message])
end
end
class TestHashDelegatorWaitForUserSelectedBlock < Minitest::Test
def setup
@@ -3862,32 +4180,39 @@
class PathUtilsTest < Minitest::Test
def test_absolute_path_returns_unchanged
absolute_path = '/usr/local/bin'
expression = 'path/to/*/directory'
- assert_equal absolute_path, PathUtils.resolve_path_or_substitute(absolute_path, expression)
+ assert_equal absolute_path,
+ PathUtils.resolve_path_or_substitute(absolute_path,
+ expression)
end
def test_relative_path_gets_substituted
relative_path = 'my_folder'
expression = 'path/to/*/directory'
expected_output = 'path/to/my_folder/directory'
- assert_equal expected_output, PathUtils.resolve_path_or_substitute(relative_path, expression)
+ assert_equal expected_output,
+ PathUtils.resolve_path_or_substitute(relative_path,
+ expression)
end
def test_path_with_no_slash_substitutes_correctly
relative_path = 'data'
expression = 'path/to/*/directory'
expected_output = 'path/to/data/directory'
- assert_equal expected_output, PathUtils.resolve_path_or_substitute(relative_path, expression)
+ assert_equal expected_output,
+ PathUtils.resolve_path_or_substitute(relative_path,
+ expression)
end
def test_empty_path_substitution
empty_path = ''
expression = 'path/to/*/directory'
expected_output = 'path/to//directory'
- assert_equal expected_output, PathUtils.resolve_path_or_substitute(empty_path, expression)
+ assert_equal expected_output,
+ PathUtils.resolve_path_or_substitute(empty_path, expression)
end
# Test formatting a string containing UTF-8 characters
def test_format_utf8_characters
input = 'Unicode test: Δ, ΓΆ, π», and π are fun!'
@@ -3932,10 +4257,11 @@
end
private
def prompt_for_filespec_with_wildcard(filespec)
- puts format(@delegate_object[:prompt_show_expr_format], { expr: filespec })
+ puts format(@delegate_object[:prompt_show_expr_format],
+ { expr: filespec })
puts @delegate_object[:prompt_enter_filespec]
begin
input = gets.chomp
PathUtils.resolve_path_or_substitute(input, filespec)