lib/markdown_exec.rb in markdown_exec-1.4.1 vs lib/markdown_exec.rb in markdown_exec-1.5
- old
+ new
@@ -36,14 +36,10 @@
$stderr.sync = true
$stdout.sync = true
MDE_HISTORY_ENV_NAME = 'MDE_MENU_HISTORY'
-# macros
-#
-LOAD_FILE = true
-
# custom error: file specified is missing
#
class FileMissingError < StandardError; end
# hash with keys sorted by name
@@ -61,10 +57,21 @@
transform_keys(&:to_sym)
end
end
end
+class LoadFile
+ Load = true
+ Reuse = false
+end
+
+class MenuState
+ BACK = :back
+ CONTINUE = :continue
+ EXIT = :exit
+end
+
# integer value for comparison
#
def options_fetch_display_level(options)
options.fetch(:display_level, 1)
end
@@ -108,20 +115,27 @@
def dp(str)
lout " => #{str}", level: DISPLAY_LEVEL_DEBUG
end
+def rpry
+ require 'pry-nav'
+ require 'pry-stack_explorer'
+end
+
public
# :reek:UtilityFunction
-def list_recent_output(saved_stdout_folder, saved_stdout_glob, list_count)
+def list_recent_output(saved_stdout_folder, saved_stdout_glob,
+ list_count)
SavedFilesMatcher.most_recent_list(saved_stdout_folder,
saved_stdout_glob, list_count)
end
# :reek:UtilityFunction
-def list_recent_scripts(saved_script_folder, saved_script_glob, list_count)
+def list_recent_scripts(saved_script_folder, saved_script_glob,
+ list_count)
SavedFilesMatcher.most_recent_list(saved_script_folder,
saved_script_glob, list_count)
end
# convert regex match groups to a hash with symbol keys
@@ -172,14 +186,14 @@
# :reek:IrresponsibleModule
FNR11 = '/'
FNR12 = ',~'
SHELL_COLOR_OPTIONS = {
- BLOCK_TYPE_BASH => :menu_bash_color,
- BLOCK_TYPE_LINK => :menu_link_color,
- BLOCK_TYPE_OPTS => :menu_opts_color,
- BLOCK_TYPE_VARS => :menu_vars_color
+ BlockType::BASH => :menu_bash_color,
+ BlockType::LINK => :menu_link_color,
+ BlockType::OPTS => :menu_opts_color,
+ BlockType::VARS => :menu_vars_color
}.freeze
##
#
# rubocop:disable Layout/LineLength
@@ -207,51 +221,87 @@
@option_parser = nil
@options = options
@prompt = tty_prompt_without_disabled_symbol
end
+ # Adds Back and Exit options to the CLI menu
+ #
+ # @param blocks_in_file [Array] The current blocks in the menu
+ def add_menu_chrome_blocks!(blocks_in_file)
+ return unless @options[:menu_link_format].present?
+
+ if @options[:menu_with_back] && history_state_exist?
+ append_chrome_block(blocks_in_file, MenuState::BACK)
+ end
+ if @options[:menu_with_exit]
+ append_chrome_block(blocks_in_file, MenuState::EXIT)
+ end
+ append_divider(blocks_in_file, @options, :initial)
+ append_divider(blocks_in_file, @options, :final)
+ end
+
##
# Appends a summary of a block (FCB) to the blocks array.
#
def append_block_summary(blocks, fcb, opts)
## enhance fcb with block summary
#
blocks.push get_block_summary(opts, fcb)
end
- ##
- # Appends a final divider to the blocks array if it is specified in options.
+ # Appends a chrome block, which is a menu option for Back or Exit
#
- def append_final_divider(blocks, opts)
- return unless opts[:menu_divider_format].present? && opts[:menu_final_divider].present?
+ # @param blocks_in_file [Array] The current blocks in the menu
+ # @param type [Symbol] The type of chrome block to add (:back or :exit)
+ def append_chrome_block(blocks_in_file, type)
+ case type
+ when MenuState::BACK
+ state = history_state_partition(@options)
+ @hs_curr = state[:unit]
+ @hs_rest = state[:rest]
+ option_name = @options[:menu_option_back_name]
+ insert_at_top = @options[:menu_back_at_top]
+ when MenuState::EXIT
+ option_name = @options[:menu_option_exit_name]
+ insert_at_top = @options[:menu_exit_at_top]
+ end
- blocks.push FCB.new(
- { chrome: true,
- disabled: '',
- dname: format(opts[:menu_divider_format],
- opts[:menu_final_divider])
- .send(opts[:menu_divider_color].to_sym),
- oname: opts[:menu_final_divider] }
+ formatted_name = format(@options[:menu_link_format],
+ safeval(option_name))
+ chrome_block = FCB.new(
+ chrome: true,
+ dname: formatted_name.send(@options[:menu_link_color].to_sym),
+ oname: formatted_name
)
+
+ if insert_at_top
+ blocks_in_file.unshift(chrome_block)
+ else
+ blocks_in_file.push(chrome_block)
+ end
end
- ##
- # Appends an initial divider to the blocks array if it is specified in options.
- #
- def append_initial_divider(blocks, opts)
- return unless opts[:menu_initial_divider].present?
+ # Appends a divider to the blocks array.
+ # @param blocks [Array] The array of block elements.
+ # @param opts [Hash] Options containing divider configuration.
+ # @param position [Symbol] :initial or :final divider position.
+ def append_divider(blocks, opts, position)
+ divider_key = position == :initial ? :menu_initial_divider : :menu_final_divider
+ unless opts[:menu_divider_format].present? && opts[divider_key].present?
+ return
+ end
- blocks.push FCB.new({
- # name: '',
- chrome: true,
- dname: format(
- opts[:menu_divider_format],
- opts[:menu_initial_divider]
- ).send(opts[:menu_divider_color].to_sym),
- oname: opts[:menu_initial_divider],
- disabled: '' # __LINE__.to_s
- })
+ oname = format(opts[:menu_divider_format],
+ safeval(opts[divider_key]))
+ divider = FCB.new(
+ chrome: true,
+ disabled: '',
+ dname: oname.send(opts[:menu_divider_color].to_sym),
+ oname: oname
+ )
+
+ position == :initial ? blocks.unshift(divider) : blocks.push(divider)
end
# Execute a code block after approval and provide user interaction options.
#
# This method displays required code blocks, asks for user approval, and
@@ -259,18 +309,16 @@
# 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 approve_and_execute_block(opts, mdoc)
- selected = mdoc.get_block_by_name(opts[:block_name])
-
- if selected.fetch(:shell, '') == BLOCK_TYPE_LINK
+ def approve_and_execute_block(selected, opts, mdoc)
+ if selected.fetch(:shell, '') == BlockType::LINK
handle_shell_link(opts, selected.fetch(:body, ''), mdoc)
- elsif opts.fetch(:back, false)
+ elsif opts.fetch(:s_back, false)
handle_back_link(opts)
- elsif selected[:shell] == BLOCK_TYPE_OPTS
+ elsif selected[:shell] == BlockType::OPTS
handle_shell_opts(opts, selected)
else
handle_remainder_blocks(mdoc, opts, selected)
end
end
@@ -300,14 +348,27 @@
item_default
else
env_str(item[:env_var],
default: OptionValue.for_hash(item_default))
end
- [item[:opt_name], item[:proccode] ? item[:proccode].call(value) : value]
+ [item[:opt_name],
+ item[:proccode] ? item[:proccode].call(value) : value]
end.compact.to_h
end
+ # Finds the first hash-like element within an enumerable collection where the specified key
+ # matches the given value. Returns a default value if no match is found.
+ #
+ # @param blocks [Enumerable] An enumerable collection of hash-like objects.
+ # @param key [Object] The key to look up in each hash-like object.
+ # @param value [Object] The value to compare against the value associated with the key.
+ # @param default [Object] The default value to return if no match is found (optional).
+ # @return [Object, nil] The found hash-like object, or the default value if no match is found.
+ def block_find(blocks, key, value, default = nil)
+ blocks.find { |item| item[key] == value } || default
+ end
+
def blocks_per_opts(blocks, opts)
return blocks if opts[:struct]
blocks.map do |block|
block.fetch(:text, nil) || block.oname
@@ -345,11 +406,11 @@
# @param opts [Hash] Options hash containing configuration settings.
# @param mdoc [YourMDocClass] An instance of the MDoc class.
# @return [Array<String>] Required code blocks as an array of lines.
def collect_required_code_lines(mdoc, selected, opts: {})
# Apply hash in opts block to environment variables
- if selected[:shell] == BLOCK_TYPE_VARS
+ if selected[:shell] == BlockType::VARS
data = YAML.load(selected[:body].join("\n"))
data.each_key do |key|
ENV[key] = value = data[key].to_s
next unless opts[:menu_vars_set_format].present?
@@ -359,11 +420,12 @@
value: value }
).send(opts[:menu_vars_set_color].to_sym)
end
end
- required = mdoc.collect_recursively_required_code(opts[:block_name], opts: opts)
+ required = mdoc.collect_recursively_required_code(opts[:block_name],
+ opts: opts)
read_required_blocks_from_temp_file + required[:code]
end
def cfile
@cfile ||= CachedNestedFileReader.new(
@@ -415,10 +477,24 @@
@execute_error = err
@execute_files[EF_STDERR] += [@execute_error_message]
fout "Error ENOENT: #{err.inspect}"
end
+ def command_or_user_selected_block(blocks_in_file, blocks_menu, default,
+ opts)
+ if opts[:block_name].present?
+ block = blocks_in_file.find do |item|
+ item[:oname] == opts[:block_name]
+ end
+ else
+ block, state = wait_for_user_selected_block(blocks_in_file, blocks_menu, default,
+ opts)
+ end
+
+ [block, state]
+ end
+
def copy_to_clipboard(required_lines)
text = required_lines.flatten.join($INPUT_RECORD_SEPARATOR)
Clipboard.copy(text)
fout "Clipboard updated: #{required_lines.count} blocks," \
" #{required_lines.flatten.count} lines," \
@@ -432,11 +508,52 @@
cnt += 1 if line.match(fenced_start_and_end_regex)
end
cnt / 2
end
- def create_and_write_file_with_permissions(file_path, content, chmod_value)
+ ##
+ # Creates and adds a formatted block to the blocks array based on the provided match and format options.
+ # @param blocks [Array] The array of blocks to add the new block to.
+ # @param fcb [FCB] The file control block containing the line to match against.
+ # @param match_data [MatchData] The match data containing named captures for formatting.
+ # @param format_option [String] The format string to be used for the new block.
+ # @param color_method [Symbol] The color method to apply to the block's display name.
+ def create_and_add_chrome_block(blocks, _fcb, match_data, format_option,
+ color_method)
+ oname = format(format_option,
+ match_data.named_captures.transform_keys(&:to_sym))
+ blocks.push FCB.new(
+ chrome: true,
+ disabled: '',
+ dname: oname.send(color_method),
+ oname: oname
+ )
+ end
+
+ ##
+ # Processes lines within the file and converts them into blocks if they match certain criteria.
+ # @param blocks [Array] The array to append new blocks to.
+ # @param fcb [FCB] The file control block being processed.
+ # @param opts [Hash] Options containing configuration for line processing.
+ # @param use_chrome [Boolean] Indicates if the chrome styling should be applied.
+ def create_and_add_chrome_blocks(blocks, fcb, opts, use_chrome)
+ return unless use_chrome
+
+ if opts[:menu_note_match].present? && (mbody = fcb.body[0].match opts[:menu_note_match])
+ create_and_add_chrome_block(blocks, fcb, mbody, opts[:menu_note_format],
+ opts[:menu_note_color].to_sym)
+ elsif opts[:menu_divider_match].present? && (mbody = fcb.body[0].match opts[:menu_divider_match])
+ create_and_add_chrome_block(blocks, fcb, mbody, opts[:menu_divider_format],
+ opts[:menu_divider_color].to_sym)
+ elsif opts[:menu_task_match].present? && (mbody = fcb.body[0].match opts[:menu_task_match])
+ create_and_add_chrome_block(blocks, fcb, mbody, opts[:menu_task_format],
+ opts[:menu_task_color].to_sym)
+ end
+ end
+
+ def create_and_write_file_with_permissions(file_path, content,
+ chmod_value)
dirname = File.dirname(file_path)
FileUtils.mkdir_p dirname
File.write(file_path, content)
return if chmod_value.zero?
@@ -448,25 +565,42 @@
# Clears the environment variable after deletion.
#
def delete_required_temp_file
temp_blocks_file_path = ENV.fetch('MDE_LINK_REQUIRED_FILE', nil)
- return if temp_blocks_file_path.nil? || temp_blocks_file_path.empty?
+ if temp_blocks_file_path.nil? || temp_blocks_file_path.empty?
+ return
+ end
FileUtils.rm_f(temp_blocks_file_path)
clear_required_file
end
+ # Derives a title from the body of an FCB object.
+ # @param fcb [Object] The FCB object whose title is to be derived.
+ # @return [String] The derived title.
+ def derive_title_from_body(fcb)
+ body_content = fcb&.body
+ return '' unless body_content
+
+ if body_content.count == 1
+ body_content.first
+ else
+ format_multiline_body_as_title(body_content)
+ end
+ end
+
## Determines the correct filename to use for searching files
#
def determine_filename(specified_filename: nil, specified_folder: nil, default_filename: nil,
default_folder: nil, filetree: nil)
if specified_filename&.present?
return specified_filename if specified_filename.start_with?('/')
- File.join(specified_folder || default_folder, specified_filename)
+ File.join(specified_folder || default_folder,
+ specified_filename)
elsif specified_folder&.present?
File.join(specified_folder,
filetree ? @options[:md_filename_match] : @options[:md_filename_glob])
else
File.join(default_folder, default_filename)
@@ -484,34 +618,36 @@
def execute_approved_block(opts, required_lines)
write_command_file(opts, required_lines)
command_execute(
opts,
required_lines.flatten.join("\n"),
- args: opts.fetch(:pass_args, [])
+ args: opts.fetch(:s_pass_args, [])
)
initialize_and_save_execution_output
output_execution_summary
output_execution_result
end
# Reports and executes block logic
def execute_block_logic(files)
@options[:filename] = select_document_if_multiple(files)
- select_approve_and_execute_block({
- bash: true,
- struct: true
- })
+ select_approve_and_execute_block({ bash: true,
+ struct: true })
end
## Executes the block specified in the options
#
def execute_block_with_error_handling(rest)
finalize_cli_argument_processing(rest)
- @options[:cli_rest] = rest
+ @options[:s_cli_rest] = rest
execute_code_block_based_on_options(@options)
rescue FileMissingError => err
puts "File missing: #{err}"
+ rescue StandardError => err
+ warn(error = "ERROR ** MarkParse.execute_block_with_error_handling(); #{err.inspect}")
+ binding.pry if $tap_enable
+ raise ArgumentError, error
end
# Main method to execute a block based on options and block_name
def execute_code_block_based_on_options(options)
options = calculated_options.merge(options)
@@ -519,11 +655,12 @@
simple_commands = {
doc_glob: -> { fout options[:md_filename_glob] },
list_blocks: lambda do
fout_list (files.map do |file|
- make_block_labels(filename: file, struct: true)
+ menu_with_block_labels(filename: file,
+ struct: true)
end).flatten(1)
end,
list_default_yaml: -> { fout_list list_default_yaml },
list_docs: -> { fout_list files },
list_default_env: -> { fout_list list_default_env },
@@ -569,19 +706,10 @@
end
end
false
end
- ##
- # Determines the types of blocks to select based on the filter.
- #
- def filter_block_types
- ## return type of blocks to select
- #
- %i[blocks line]
- end
-
## post-parse options configuration
#
def finalize_cli_argument_processing(rest)
## position 0: file or folder (optional)
#
@@ -599,10 +727,19 @@
#
block_name = rest.shift
@options[:block_name] = block_name if block_name.present?
end
+ # Formats multiline body content as a title string.
+ # @param body_lines [Array<String>] The lines of body content.
+ # @return [String] Formatted title.
+ def format_multiline_body_as_title(body_lines)
+ body_lines.map.with_index do |line, index|
+ index.zero? ? line : " #{line}"
+ end.join("\n") << "\n"
+ end
+
## summarize blocks
#
def get_block_summary(call_options, fcb)
opts = optsmerge call_options
# return fcb.body unless opts[:struct]
@@ -612,13 +749,16 @@
titlexcall = if fcb.call
fcb.title.sub("%#{fcb.call}", '')
else
fcb.title
end
- bm = extract_named_captures_from_option(titlexcall, opts[:block_name_match])
- fcb.stdin = extract_named_captures_from_option(titlexcall, opts[:block_stdin_scan])
- fcb.stdout = extract_named_captures_from_option(titlexcall, opts[:block_stdout_scan])
+ bm = extract_named_captures_from_option(titlexcall,
+ opts[:block_name_match])
+ fcb.stdin = extract_named_captures_from_option(titlexcall,
+ opts[:block_stdin_scan])
+ fcb.stdout = extract_named_captures_from_option(titlexcall,
+ opts[:block_stdout_scan])
shell_color_option = SHELL_COLOR_OPTIONS[fcb[:shell]]
fcb.title = fcb.oname = bm && bm[1] ? bm[:title] : titlexcall
fcb.dname = if shell_color_option && opts[shell_color_option].present?
fcb.oname.send(opts[shell_color_option].to_sym)
@@ -626,95 +766,97 @@
fcb.oname
end
fcb
end
- ##
- # Handles errors that occur during the block listing process.
- #
- def handle_error(err)
- warn(error = "ERROR ** MarkParse.list_blocks_in_file(); #{err.inspect}")
- warn(caller[0..4])
- raise StandardError, error
- end
-
# Handles the link-back operation.
#
# @param opts [Hash] Configuration options hash.
- # @return [Array<Symbol, String>] A tuple containing a LOAD_FILE flag and an empty string.
+ # @return [Array<Symbol, String>] A tuple containing a LoadFile flag and an empty string.
def handle_back_link(opts)
history_state_pop(opts)
- [LOAD_FILE, '']
+ [LoadFile::Load, '']
end
# Handles the execution and display of remainder blocks from a selected menu item.
#
# @param mdoc [Object] Document object containing code blocks.
# @param opts [Hash] Configuration options hash.
# @param selected [Hash] Selected item from the menu.
- # @return [Array<Symbol, String>] A tuple containing a LOAD_FILE flag and an empty string.
+ # @return [Array<Symbol, String>] A tuple containing a LoadFile flag and an empty string.
# @note The function can prompt the user for approval before executing code if opts[:user_must_approve] is true.
def handle_remainder_blocks(mdoc, opts, selected)
- required_lines = collect_required_code_lines(mdoc, selected, opts: opts)
+ required_lines = collect_required_code_lines(mdoc, selected,
+ opts: opts)
if opts[:output_script] || opts[:user_must_approve]
display_required_code(opts, required_lines)
end
- allow = opts[:user_must_approve] ? prompt_for_user_approval(opts, required_lines) : true
- opts[:ir_approve] = allow
- execute_approved_block(opts, required_lines) if opts[:ir_approve]
+ allow = if opts[:user_must_approve]
+ prompt_for_user_approval(opts,
+ required_lines)
+ else
+ true
+ end
+ opts[:s_ir_approve] = allow
+ if opts[:s_ir_approve]
+ execute_approved_block(opts,
+ required_lines)
+ end
- [!LOAD_FILE, '']
+ [LoadFile::Reuse, '']
end
# Handles the link-shell operation.
#
# @param opts [Hash] Configuration options hash.
# @param body [Array<String>] The body content.
# @param mdoc [Object] Document object containing code blocks.
- # @return [Array<Symbol, String>] A tuple containing a LOAD_FILE flag and a block name.
+ # @return [Array<Symbol, String>] A tuple containing a LoadFile flag and a block name.
def handle_shell_link(opts, body, mdoc)
data = body.present? ? YAML.load(body.join("\n")) : {}
data_file = data.fetch('file', nil)
- return [!LOAD_FILE, ''] unless data_file
+ return [LoadFile::Reuse, ''] unless data_file
history_state_push(mdoc, data_file, opts)
data.fetch('vars', []).each do |var|
ENV[var[0]] = var[1].to_s
end
- [LOAD_FILE, data.fetch('block', '')]
+ [LoadFile::Load, data.fetch('block', '')]
end
# Handles options for the shell.
#
# @param opts [Hash] Configuration options hash.
# @param selected [Hash] Selected item from the menu.
- # @return [Array<Symbol, String>] A tuple containing a NOT_LOAD_FILE flag and an empty string.
- def handle_shell_opts(opts, selected)
+ # @return [Array<Symbol, String>] A tuple containing a LoadFile::Reuse flag and an empty string.
+ def handle_shell_opts(opts, selected, tgt2 = nil)
data = YAML.load(selected[:body].join("\n"))
data.each_key do |key|
- opts[key.to_sym] = value = data[key].to_s
+ opts[key.to_sym] = value = data[key]
+ tgt2[key.to_sym] = value if tgt2
next unless opts[:menu_opts_set_format].present?
print format(
opts[:menu_opts_set_format],
{ key: key,
value: value }
).send(opts[:menu_opts_set_color].to_sym)
end
- [!LOAD_FILE, '']
+ [LoadFile::Reuse, '']
end
# Handles reading and processing lines from a given IO stream
#
# @param stream [IO] The IO stream to read from (e.g., stdout, stderr, stdin).
# @param file_type [Symbol] The type of file to which the stream corresponds.
def handle_stream(opts, stream, file_type, swap: false)
Thread.new do
until (line = stream.gets).nil?
- @execute_files[file_type] = @execute_files[file_type] + [line.strip]
+ @execute_files[file_type] =
+ @execute_files[file_type] + [line.strip]
print line if opts[:output_stdout]
yield line if block_given?
end
rescue IOError
#d 'stdout IOError, thread killed, do nothing'
@@ -753,11 +895,12 @@
## Sets up the options and returns the parsed arguments
#
def initialize_and_parse_cli_options
@options = base_options
- read_configuration_file!(@options, ".#{MarkdownExec::APP_NAME.downcase}.yml")
+ read_configuration_file!(@options,
+ ".#{MarkdownExec::APP_NAME.downcase}.yml")
@option_parser = OptionParser.new do |opts|
executable_name = File.basename($PROGRAM_NAME)
opts.banner = [
"#{MarkdownExec::APP_NAME}" \
@@ -771,21 +914,22 @@
end
@option_parser.load
@option_parser.environment
rest = @option_parser.parse!(arguments_for_mde)
- @options[:pass_args] = ARGV[rest.count + 1..]
+ @options[:s_pass_args] = ARGV[rest.count + 1..]
rest
end
def initialize_and_save_execution_output
return unless @options[:save_execution_output]
@options[:logged_stdout_filename] =
SavedAsset.stdout_name(blockname: @options[:block_name],
- filename: File.basename(@options[:filename], '.*'),
+ filename: File.basename(@options[:filename],
+ '.*'),
prefix: @options[:logged_stdout_filename_prefix],
time: Time.now.utc)
@logged_stdout_filespec =
@options[:logged_stdout_filespec] =
@@ -810,87 +954,60 @@
def iter_blocks_in_file(opts = {}, &block)
return unless check_file_existence(opts[:filename])
state = initialize_state(opts)
- # get type of messages to select
selected_messages = yield :filter
cfile.readlines(opts[:filename]).each do |line|
next unless line
- update_line_and_block_state(line, state, opts, selected_messages, &block)
+ update_line_and_block_state(line, state, opts, selected_messages,
+ &block)
end
end
##
- # Returns a list of blocks in a given file, including dividers, tasks, and other types of blocks.
- # The list can be customized via call_options and options_block.
- #
- # @param call_options [Hash] Options passed as an argument.
- # @param options_block [Proc] Block for dynamic option manipulation.
- # @return [Array<FCB>] An array of FCB objects representing the blocks.
- #
- def list_blocks_in_file(call_options = {}, &options_block)
- opts = optsmerge(call_options, options_block)
- use_chrome = !opts[:no_chrome]
-
- blocks = []
- append_initial_divider(blocks, opts) if use_chrome
-
- iter_blocks_in_file(opts) do |btype, fcb|
- case btype
- when :filter
- filter_block_types
- when :line
- process_line_blocks(blocks, fcb, opts, use_chrome)
- when :blocks
- append_block_summary(blocks, fcb, opts)
- end
- end
-
- append_final_divider(blocks, opts) if use_chrome
- blocks
- rescue StandardError => err
- handle_error(err)
- end
-
- ##
- # Processes lines within the file and converts them into blocks if they match certain criteria.
- #
- def process_line_blocks(blocks, fcb, opts, use_chrome)
- ## convert line to block
- #
- if opts[:menu_divider_match].present? &&
- (mbody = fcb.body[0].match opts[:menu_divider_match])
- if use_chrome
- blocks.push FCB.new(
- { chrome: true,
- disabled: '',
- dname: format(opts[:menu_divider_format],
- mbody[:name]).send(opts[:menu_divider_color].to_sym),
- oname: mbody[:name] }
- )
- end
- elsif opts[:menu_task_match].present? &&
- (fcb.body[0].match opts[:menu_task_match])
- if use_chrome
- blocks.push FCB.new(
- { chrome: true,
- disabled: '',
- dname: format(
- opts[:menu_task_format],
- $~.named_captures.transform_keys(&:to_sym)
- ).send(opts[:menu_task_color].to_sym),
- oname: format(
- opts[:menu_task_format],
- $~.named_captures.transform_keys(&:to_sym)
- ) }
- )
- end
+ # Returns a lambda expression based on the given procname.
+ # @param procname [String] The name of the process to generate a lambda for.
+ # @param options [Hash] The options hash, necessary for some lambdas to access.
+ # @return [Lambda] The corresponding lambda expression.
+ def lambda_for_procname(procname, options)
+ case procname
+ when 'debug'
+ lambda { |value|
+ tap_config value: value
+ }
+ when 'exit'
+ ->(_) { exit }
+ when 'help'
+ lambda { |_|
+ fout menu_help
+ exit
+ }
+ when 'path'
+ ->(value) { read_configuration_file!(options, value) }
+ when 'show_config'
+ lambda { |_|
+ finalize_cli_argument_processing(options)
+ fout options.sort_by_key.to_yaml
+ }
+ when 'val_as_bool'
+ lambda { |value|
+ value.instance_of?(::String) ? (value.chomp != '0') : value
+ }
+ when 'val_as_int'
+ ->(value) { value.to_i }
+ when 'val_as_str'
+ ->(value) { value.to_s }
+ when 'version'
+ lambda { |_|
+ fout MarkdownExec::VERSION
+ exit
+ }
else
- # line not added
+ procname
end
end
def list_default_env
menu_iter do |item|
@@ -942,44 +1059,74 @@
## output type (body string or full object) per option struct and bash
#
def list_named_blocks_in_file(call_options = {}, &options_block)
opts = optsmerge call_options, options_block
+ blocks_per_opts(
+ menu_from_file(opts.merge(struct: true)).select do |fcb|
+ Filter.fcb_select?(opts.merge(no_chrome: true), fcb)
+ end, opts
+ )
+ end
- blocks = list_blocks_in_file(opts.merge(struct: true)).select do |fcb|
- # fcb.fetch(:name, '') != '' && Filter.fcb_select?(opts, fcb)
- Filter.fcb_select?(opts.merge(no_chrome: true), fcb)
+ # return true if values were modified
+ # execute block once per filename
+ #
+ def load_auto_blocks(opts, blocks_in_file)
+ return unless opts[:document_load_opts_block_name].present?
+ return if opts[:s_most_recent_filename] == opts[:filename]
+
+ block = block_find(blocks_in_file, :oname,
+ opts[:document_load_opts_block_name])
+ return unless block
+
+ handle_shell_opts(opts, block, @options)
+ opts[:s_most_recent_filename] = opts[:filename]
+ true
+ end
+
+ def mdoc_and_menu_from_file(opts)
+ menu_blocks = menu_from_file(opts.merge(struct: true))
+ mdoc = MDoc.new(menu_blocks) do |nopts|
+ opts.merge!(nopts)
end
- blocks_per_opts(blocks, opts)
+ [menu_blocks, mdoc]
end
## Handles the file loading and returns the blocks in the file and MDoc instance
#
- def load_file_and_prepare_menu(opts)
- blocks_in_file = list_blocks_in_file(opts.merge(struct: true))
- mdoc = MDoc.new(blocks_in_file) do |nopts|
- opts.merge!(nopts)
+ def mdoc_menu_and_selected_from_file(opts)
+ blocks_in_file, mdoc = mdoc_and_menu_from_file(opts)
+ if load_auto_blocks(opts, blocks_in_file)
+ # recreate menu with new options
+ #
+ blocks_in_file, mdoc = mdoc_and_menu_from_file(opts)
end
+
blocks_menu = mdoc.fcbs_per_options(opts.merge(struct: true))
+ add_menu_chrome_blocks!(blocks_menu)
[blocks_in_file, blocks_menu, mdoc]
end
- def make_block_labels(call_options = {})
- opts = options.merge(call_options)
- list_blocks_in_file(opts).map do |fcb|
- BlockLabel.make(
- filename: opts[:filename],
- headings: fcb.fetch(:headings, []),
- menu_blocks_with_docname: opts[:menu_blocks_with_docname],
- menu_blocks_with_headings: opts[:menu_blocks_with_headings],
- title: fcb[:title],
- text: fcb[:text],
- body: fcb[:body]
- )
- end.compact
+ def menu_chrome_colored_option(opts,
+ option_symbol = :menu_option_back_name)
+ if opts[:menu_chrome_color]
+ menu_chrome_formatted_option(opts,
+ option_symbol).send(opts[:menu_chrome_color].to_sym)
+ else
+ menu_chrome_formatted_option(opts, option_symbol)
+ end
end
+ def menu_chrome_formatted_option(opts,
+ option_symbol = :menu_option_back_name)
+ val1 = safeval(opts.fetch(option_symbol, ''))
+ val1 unless opts[:menu_chrome_format]
+
+ format(opts[:menu_chrome_format], val1)
+ end
+
def menu_export(data = menu_for_optparse)
data.map do |item|
item.delete(:procname)
item
end.to_yaml
@@ -993,80 +1140,80 @@
when :filter
%i[blocks line]
when :line
if options[:menu_divider_match] &&
(mbody = fcb.body[0].match(options[:menu_divider_match]))
- menu.push FCB.new({ dname: mbody[:name], oname: mbody[:name], disabled: '' })
+ menu.push FCB.new({ dname: mbody[:name], oname: mbody[:name],
+ disabled: '' })
end
+ if options[:menu_note_match] &&
+ (mbody = fcb.body[0].match(options[:menu_note_match]))
+ menu.push FCB.new({ dname: mbody[:name], oname: mbody[:name],
+ disabled: '' })
+ end
when :blocks
menu += [fcb.oname]
end
end
menu
end
- # :reek:DuplicateMethodCall
- # :reek:NestedIterators
+ ##
+ # Generates a menu suitable for OptionParser from the menu items defined in YAML format.
+ # @return [Array<Hash>] The array of option hashes for OptionParser.
def menu_for_optparse
menu_from_yaml.map do |menu_item|
menu_item.merge(
- {
- opt_name: menu_item[:opt_name]&.to_sym,
- proccode: case menu_item[:procname]
- when 'debug'
- lambda { |value|
- tap_config value: value
- }
- when 'exit'
- lambda { |_|
- exit
- }
- when 'help'
- lambda { |_|
- fout menu_help
- exit
- }
- when 'path'
- lambda { |value|
- read_configuration_file!(options, value)
- }
- when 'show_config'
- lambda { |_|
- finalize_cli_argument_processing(options)
- fout options.sort_by_key.to_yaml
- }
- when 'val_as_bool'
- lambda { |value|
- value.instance_of?(::String) ? (value.chomp != '0') : value
- }
- when 'val_as_int'
- ->(value) { value.to_i }
- when 'val_as_str'
- ->(value) { value.to_s }
- when 'version'
- lambda { |_|
- fout MarkdownExec::VERSION
- exit
- }
- else
- menu_item[:procname]
- end
- }
+ opt_name: menu_item[:opt_name]&.to_sym,
+ proccode: lambda_for_procname(menu_item[:procname], options)
)
end
end
+ ##
+ # Returns a list of blocks in a given file, including dividers, tasks, and other types of blocks.
+ # The list can be customized via call_options and options_block.
+ #
+ # @param call_options [Hash] Options passed as an argument.
+ # @param options_block [Proc] Block for dynamic option manipulation.
+ # @return [Array<FCB>] An array of FCB objects representing the blocks.
+ #
+ def menu_from_file(call_options = {},
+ &options_block)
+ opts = optsmerge(call_options, options_block)
+ use_chrome = !opts[:no_chrome]
+
+ blocks = []
+ iter_blocks_in_file(opts) do |btype, fcb|
+ case btype
+ when :blocks
+ append_block_summary(blocks, fcb, opts)
+ when :filter # what btypes are responded to?
+ %i[blocks line]
+ when :line
+ create_and_add_chrome_blocks(blocks, fcb, opts, use_chrome)
+ end
+ end
+ blocks
+ rescue StandardError => err
+ warn(error = "ERROR ** MarkParse.menu_from_file(); #{err.inspect}")
+ warn(caller[0..4])
+ raise StandardError, error
+ end
+
def menu_help
@option_parser.help
end
def menu_iter(data = menu_for_optparse, &block)
data.map(&block)
end
def menu_option_append(opts, options, item)
- return unless item[:long_name].present? || item[:short_name].present?
+ unless item[:long_name].present? || item[:short_name].present?
+ return
+ end
opts.on(*[
# - long name
if item[:long_name].present?
"--#{item[:long_name]}#{item[:arg_name].present? ? " #{item[:arg_name]}" : ''}"
@@ -1075,11 +1222,13 @@
# - short name
item[:short_name].present? ? "-#{item[:short_name]}" : nil,
# - description and default
[item[:description],
- ("[#{value_for_cli item[:default]}]" if item[:default].present?)].compact.join(' '),
+ (if item[:default].present?
+ "[#{value_for_cli item[:default]}]"
+ end)].compact.join(' '),
# apply proccode, if present, to value
# save value to options hash if option is named
#
lambda { |value|
@@ -1088,13 +1237,28 @@
end
}
].compact)
end
+ def menu_with_block_labels(call_options = {})
+ opts = options.merge(call_options)
+ menu_from_file(opts).map do |fcb|
+ BlockLabel.make(
+ filename: opts[:filename],
+ headings: fcb.fetch(:headings, []),
+ menu_blocks_with_docname: opts[:menu_blocks_with_docname],
+ menu_blocks_with_headings: opts[:menu_blocks_with_headings],
+ title: fcb[:title],
+ text: fcb[:text],
+ body: fcb[:body]
+ )
+ end.compact
+ end
+
def next_block_name_from_command_line_arguments(opts)
- if opts[:cli_rest].present?
- opts[:block_name] = opts[:cli_rest].pop
+ if opts[:s_cli_rest].present?
+ opts[:block_name] = opts[:s_cli_rest].pop
false # repeat_menu
else
true # repeat_menu
end
end
@@ -1117,11 +1281,14 @@
@options[:block_name]].join(' '),
DISPLAY_LEVEL_ADMIN]]
[['Script', :saved_filespec],
['StdOut', :logged_stdout_filespec]].each do |label, name|
- oq << [label, @options[name], DISPLAY_LEVEL_ADMIN] if @options[name]
+ if @options[name]
+ oq << [label, @options[name],
+ DISPLAY_LEVEL_ADMIN]
+ end
end
oq.map do |label, value, level|
lout ["#{label}:".yellow, value.to_s].join(' '), level: level
end
@@ -1148,11 +1315,13 @@
# @param opts [Hash] The options hash.
# @return [Array<Hash>] The updated blocks menu.
def prepare_blocks_menu(blocks_in_file, opts)
# next if fcb.fetch(:disabled, false)
# next unless fcb.fetch(:name, '').present?
- blocks_in_file.map do |fcb|
+ replace_consecutive_blanks(blocks_in_file).map do |fcb|
+ next if Filter.prepared_not_in_menu?(opts, fcb)
+
fcb.merge!(
name: fcb.dname,
label: BlockLabel.make(
body: fcb[:body],
filename: opts[:filename],
@@ -1174,11 +1343,11 @@
def process_fenced_block(fcb, opts, selected_messages, &block)
fcb.oname = fcb.dname = fcb.title || ''
return unless fcb.body
- set_fcb_title(fcb)
+ update_title_from_body(fcb)
if block &&
selected_messages.include?(:blocks) &&
Filter.fcb_select?(opts, fcb)
block.call :blocks, fcb
@@ -1192,10 +1361,17 @@
fcb = FCB.new
fcb.body = [line]
block.call(:line, fcb)
end
+ class MenuOptions
+ YES = 1
+ NO = 2
+ SCRIPT_TO_CLIPBOARD = 3
+ SAVE_SCRIPT = 4
+ end
+
##
# Presents a menu to the user for approving an action and performs additional tasks based on the selection.
# The function provides options for approval, rejection, copying data to clipboard, or saving data to a file.
#
# @param opts [Hash] A hash containing various options for the menu.
@@ -1209,48 +1385,44 @@
#
# @return [Boolean] Returns true if the user approves (selects 'Yes'), false otherwise.
##
def prompt_for_user_approval(opts, required_lines)
# Present a selection menu for user approval.
- sel = @prompt.select(opts[:prompt_approve_block], filter: true) do |menu|
- menu.default 1
- menu.choice opts[:prompt_yes], 1
- menu.choice opts[:prompt_no], 2
- menu.choice opts[:prompt_script_to_clipboard], 3
- menu.choice opts[:prompt_save_script], 4
+
+ sel = @prompt.select(opts[:prompt_approve_block],
+ filter: true) do |menu|
+ menu.default MenuOptions::YES
+ menu.choice opts[:prompt_yes], MenuOptions::YES
+ menu.choice opts[:prompt_no], MenuOptions::NO
+ menu.choice opts[:prompt_script_to_clipboard],
+ MenuOptions::SCRIPT_TO_CLIPBOARD
+ menu.choice opts[:prompt_save_script], MenuOptions::SAVE_SCRIPT
end
- if sel == 3
+ if sel == MenuOptions::SCRIPT_TO_CLIPBOARD
copy_to_clipboard(required_lines)
- elsif sel == 4
+ elsif sel == MenuOptions::SAVE_SCRIPT
save_to_file(opts, required_lines)
end
- sel == 1
+ sel == MenuOptions::YES
+ rescue TTY::Reader::InputInterrupt
+ exit 1
end
- ## insert back option at head or tail
- #
- ## Adds a back option at the head or tail of a menu
- #
- def prompt_menu_add_back(items, label)
- return items unless @options[:menu_with_back] && history_state_exist?
-
- state = history_state_partition(@options)
- @hs_curr = state[:unit]
- @hs_rest = state[:rest]
- @options[:menu_back_at_top] ? [label] + items : items + [label]
- end
-
- ## insert exit option at head or tail
- #
- def prompt_menu_add_exit(items, label)
- if @options[:menu_exit_at_top]
- (@options[:menu_with_exit] ? [label] : []) + items
- else
- items + (@options[:menu_with_exit] ? [label] : [])
+ def prompt_select_continue(opts)
+ sel = @prompt.select(
+ opts[:prompt_after_bash_exec],
+ filter: true,
+ quiet: true
+ ) do |menu|
+ menu.choice opts[:prompt_yes]
+ menu.choice opts[:prompt_exit]
end
+ sel == opts[:prompt_exit] ? MenuState::EXIT : MenuState::CONTINUE
+ rescue TTY::Reader::InputInterrupt
+ exit 1
end
# :reek:UtilityFunction ### temp
def read_configuration_file!(options, configuration_path)
return unless File.exist?(configuration_path)
@@ -1265,19 +1437,39 @@
# @note Relies on the 'MDE_LINK_REQUIRED_FILE' environment variable to locate the file.
def read_required_blocks_from_temp_file
temp_blocks = []
temp_blocks_file_path = ENV.fetch('MDE_LINK_REQUIRED_FILE', nil)
- return temp_blocks if temp_blocks_file_path.nil? || temp_blocks_file_path.empty?
+ if temp_blocks_file_path.nil? || temp_blocks_file_path.empty?
+ return temp_blocks
+ end
if File.exist?(temp_blocks_file_path)
temp_blocks = File.readlines(temp_blocks_file_path, chomp: true)
end
temp_blocks
end
+ # Replace duplicate blanks (where :oname is not present) with a single blank line.
+ #
+ # @param [Array<Hash>] lines Array of hashes to process.
+ # @return [Array<Hash>] Cleaned array with consecutive blanks collapsed into one.
+ def replace_consecutive_blanks(lines)
+ lines.chunk_while do |i, j|
+ i[:oname].to_s.empty? && j[:oname].to_s.empty?
+ end.map do |chunk|
+ if chunk.any? do |line|
+ line[:oname].to_s.strip.empty?
+ end
+ chunk.first
+ else
+ chunk
+ end
+ end.flatten
+ end
+
def run
clear_required_file
execute_block_with_error_handling(initialize_and_parse_cli_options)
delete_required_temp_file
rescue StandardError => err
@@ -1291,13 +1483,21 @@
@options[:saved_script_glob])
return unless filename
saved_name_split filename
@options[:save_executed_script] = false
- select_approve_and_execute_block({})
+ select_approve_and_execute_block
end
+ def safeval(str)
+ eval(str)
+ rescue StandardError
+ warn $!
+ binding.pry if $tap_enable
+ raise StandardError, $!
+ end
+
def save_to_file(opts, required_lines)
write_command_file(opts.merge(save_executed_script: true),
required_lines)
fout "File saved: #{@options[:saved_filespec]}"
end
@@ -1318,44 +1518,52 @@
# Markdown document, obtain approval, and execute the chosen block of code.
#
# @param call_options [Hash] Initial options for the method.
# @param options_block [Block] Block of options to be merged with call_options.
# @return [Nil] Returns nil if no code block is selected or an error occurs.
- def select_approve_and_execute_block(call_options, &options_block)
- opts = optsmerge(call_options, options_block)
- repeat_menu = true && !opts[:block_name].present?
- load_file = !LOAD_FILE
- default = 1
+ def select_approve_and_execute_block(call_options = {},
+ &options_block)
+ base_opts = optsmerge(call_options, options_block)
+ repeat_menu = true && !base_opts[:block_name].present?
+ load_file = LoadFile::Reuse
+ default = nil
+ block = nil
loop do
loop do
- opts[:back] = false
- blocks_in_file, blocks_menu, mdoc = load_file_and_prepare_menu(opts)
+ opts = base_opts.dup
+ opts[:s_back] = false
+ blocks_in_file, blocks_menu, mdoc = mdoc_menu_and_selected_from_file(opts)
+ block, state = command_or_user_selected_block(blocks_in_file, blocks_menu,
+ default, opts)
+ return if state == MenuState::EXIT
- unless opts[:block_name].present?
- block_name, state = wait_for_user_selection(blocks_in_file, blocks_menu, default,
- opts)
- case state
- when :exit
- return nil
- when :back
- opts[:block_name] = block_name[:option]
- opts[:back] = true
- when :continue
- opts[:block_name] = block_name
- end
+ load_file, next_block_name = approve_and_execute_block(block, opts,
+ mdoc)
+ default = load_file == LoadFile::Load ? nil : opts[:block_name]
+ base_opts[:block_name] = opts[:block_name] = next_block_name
+ base_opts[:filename] = opts[:filename]
+
+ # user prompt to exit if the menu will be displayed again
+ #
+ if repeat_menu &&
+ block[:shell] == BlockType::BASH &&
+ opts[:pause_after_bash_exec] &&
+ prompt_select_continue(opts) == MenuState::EXIT
+ return
end
- load_file, next_block_name = approve_and_execute_block(opts, mdoc)
- default = load_file == LOAD_FILE ? 1 : opts[:block_name]
- opts[:block_name] = next_block_name
- break if state == :continue && load_file == LOAD_FILE
+ # exit current document/menu if loading next document or single block_name was specified
+ #
+ if state == MenuState::CONTINUE && load_file == LoadFile::Load
+ break
+ end
break unless repeat_menu
end
- break if load_file != LOAD_FILE
+ break if load_file == LoadFile::Reuse
- repeat_menu = next_block_name_from_command_line_arguments(opts)
+ repeat_menu = next_block_name_from_command_line_arguments(base_opts)
end
rescue StandardError => err
warn(error = "ERROR ** MarkParse.select_approve_and_execute_block(); #{err.inspect}")
warn err.backtrace
binding.pry if $tap_enable
@@ -1381,25 +1589,28 @@
end
# Presents a TTY prompt to select an option or exit, returns metadata including option and selected
def select_option_with_metadata(prompt_text, items, opts = {})
selection = @prompt.select(prompt_text,
- prompt_menu_add_exit(
- prompt_menu_add_back(
- items,
- opts[:menu_option_back_name]
- ),
- opts[:menu_option_exit_name]
- ),
+ items,
opts.merge(filter: true))
- if selection == opts[:menu_option_back_name]
- { option: selection, curr: @hs_curr, rest: @hs_rest, shell: BLOCK_TYPE_LINK }
- elsif selection == opts[:menu_option_exit_name]
- { option: selection }
- else
- { selected: selection }
- end
+
+ items.find { |item| item[:dname] == selection }
+ .merge(
+ if selection == menu_chrome_colored_option(opts,
+ :menu_option_back_name)
+ { option: selection, curr: @hs_curr, rest: @hs_rest,
+ shell: BlockType::LINK }
+ elsif selection == menu_chrome_colored_option(opts,
+ :menu_option_exit_name)
+ { option: selection }
+ else
+ { selected: selection }
+ end
+ )
+ rescue TTY::Reader::InputInterrupt
+ exit 1
end
def select_recent_output
filename = select_option_or_exit(
@options[:prompt_select_output].to_s,
@@ -1427,25 +1638,17 @@
)
return if filename.nil?
saved_name_split(filename)
- select_approve_and_execute_block({
- bash: true,
+ select_approve_and_execute_block({ bash: true,
save_executed_script: false,
- struct: true
- })
+ struct: true })
end
- # set the title of an FCB object based on its body if it is nil or empty
- def set_fcb_title(fcb)
- return unless fcb.title.nil? || fcb.title.empty?
-
- fcb.title = (fcb&.body || []).join(' ').gsub(/ +/, ' ')[0..64]
- end
-
- def start_fenced_block(opts, line, headings, fenced_start_extended_regex)
+ def start_fenced_block(opts, line, headings,
+ fenced_start_extended_regex)
fcb_title_groups = line.match(fenced_start_extended_regex).named_captures.sym_keys
rest = fcb_title_groups.fetch(:rest, '')
fcb = FCB.new
fcb.headings = headings
@@ -1474,11 +1677,15 @@
"--#{item[:long_name]}" if item[:long_name]
end.compact
end
def tty_prompt_without_disabled_symbol
- TTY::Prompt.new(interrupt: :exit, symbols: { cross: ' ' })
+ TTY::Prompt.new(interrupt: lambda {
+ puts;
+ raise TTY::Reader::InputInterrupt
+ },
+ symbols: { cross: ' ' })
end
##
# Updates the hierarchy of document headings based on the given line and existing headings.
# The function uses regular expressions specified in the `opts` to identify different levels of headings.
@@ -1521,19 +1728,21 @@
#
# @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.
##
- def update_line_and_block_state(line, state, opts, selected_messages, &block)
+ def update_line_and_block_state(line, state, opts, selected_messages,
+ &block)
if opts[:menu_blocks_with_headings]
state[:headings] =
update_document_headings(line, state[:headings], opts)
end
if line.match(state[:fenced_start_and_end_regex])
if state[:in_fenced_block]
- process_fenced_block(state[:fcb], opts, selected_messages, &block)
+ process_fenced_block(state[:fcb], opts, selected_messages,
+ &block)
state[:in_fenced_block] = false
else
state[:fcb] =
start_fenced_block(opts, line, state[:headings],
state[:fenced_start_extended_regex])
@@ -1555,30 +1764,63 @@
@options.merge! opts
end
@options
end
- ## Handles the menu interaction and returns selected block name and option state
+ # Updates the title of an FCB object from its body content if the title is nil or empty.
+ def update_title_from_body(fcb)
+ return unless fcb.title.nil? || fcb.title.empty?
+
+ fcb.title = derive_title_from_body(fcb)
+ end
+
+ def wait_for_user_selected_block(blocks_in_file, blocks_menu,
+ default, opts)
+ block, state = wait_for_user_selection(blocks_in_file, blocks_menu,
+ default, opts)
+ case state
+ when MenuState::BACK
+ opts[:block_name] = block[:dname]
+ opts[:s_back] = true
+ when MenuState::CONTINUE
+ opts[:block_name] = block[:dname]
+ end
+
+ [block, state]
+ end
+
+ ## Handles the menu interaction and returns selected block and option state
#
- def wait_for_user_selection(blocks_in_file, blocks_menu, default, opts)
+ def wait_for_user_selection(blocks_in_file, blocks_menu, default,
+ opts)
pt = opts[:prompt_select_block].to_s
bm = prepare_blocks_menu(blocks_menu, opts)
- return [nil, :exit] if bm.count.zero?
+ return [nil, MenuState::EXIT] if bm.count.zero?
- obj = select_option_with_metadata(pt, bm, opts.merge(
- default: default,
+ o2 = if default
+ opts.merge(default: default)
+ else
+ opts
+ end
+
+ obj = select_option_with_metadata(pt, bm, o2.merge(
per_page: opts[:select_page_height]
))
- case obj.fetch(:option, nil)
- when opts[:menu_option_exit_name]
- [nil, :exit]
- when opts[:menu_option_back_name]
- [obj, :back]
+
+ case obj.fetch(:oname, nil)
+ when menu_chrome_formatted_option(opts, :menu_option_exit_name)
+ [nil, MenuState::EXIT]
+ when menu_chrome_formatted_option(opts, :menu_option_back_name)
+ [obj, MenuState::BACK]
else
- label_block = blocks_in_file.find { |fcb| fcb.dname == obj[:selected] }
- [label_block.oname, :continue]
+ [obj, MenuState::CONTINUE]
end
+ rescue StandardError => err
+ warn(error = "ERROR ** MarkParse.wait_for_user_selection(); #{err.inspect}")
+ warn caller.take(3)
+ binding.pry if $tap_enable
+ raise ArgumentError, error
end
# Handles the core logic for generating the command file's metadata and content.
def write_command_file(call_options, required_lines)
return unless call_options[:save_executed_script]
@@ -1591,11 +1833,12 @@
prefix: opts[:saved_script_filename_prefix],
time: time_now)
@execute_script_filespec =
@options[:saved_filespec] =
- File.join opts[:saved_script_folder], opts[:saved_script_filename]
+ File.join opts[:saved_script_folder],
+ opts[:saved_script_filename]
shebang = if @options[:shebang]&.present?
"#{@options[:shebang]} #{@options[:shell]}\n"
else
''
@@ -1654,11 +1897,11 @@
class TestMarkParse < Minitest::Test
require 'mocha/minitest'
def test_calling_execute_approved_block_calls_command_execute_with_argument_args_value
pigeon = 'E'
- obj = { pass_args: pigeon }
+ obj = { s_pass_args: pigeon }
c = MarkdownExec::MarkParse.new
# Expect that method command_execute is called with argument args having value pigeon
c.expects(:command_execute).with(
@@ -1674,19 +1917,21 @@
def setup
@mark_parse = MarkdownExec::MarkParse.new
end
def test_set_fcb_title
- # sample input and output data for testing set_fcb_title method
+ # sample input and output data for testing update_title_from_body method
input_output_data = [
{
input: FCB.new(title: nil, body: ["puts 'Hello, world!'"]),
output: "puts 'Hello, world!'"
},
{
- input: FCB.new(title: '', body: ['def add(x, y)', ' x + y', 'end']),
- output: 'def add(x, y) x + y end'
+ input: FCB.new(title: '',
+ body: ['def add(x, y)',
+ ' x + y', 'end']),
+ output: "def add(x, y)\n x + y\n end\n"
},
{
input: FCB.new(title: 'foo', body: %w[bar baz]),
output: 'foo' # expect the title to remain unchanged
}
@@ -1695,12 +1940,22 @@
# iterate over the input and output data and
# assert that the method sets the title as expected
input_output_data.each do |data|
input = data[:input]
output = data[:output]
- @mark_parse.set_fcb_title(input)
+ @mark_parse.update_title_from_body(input)
assert_equal output, input.title
end
end
end
- end
-end
+
+ def test_select_block
+ blocks = [block1, block2]
+ menu = [m1, m2]
+
+ block, state = obj.select_block(blocks, menu, nil, {})
+
+ assert_equal block1, block
+ assert_equal MenuState::CONTINUE, state
+ end
+ end # module MarkdownExec
+end # if