markdown_exec 1.8.5 → 1.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -37,6 +37,273 @@ class String
37
37
  end
38
38
  end
39
39
 
40
+ module HashDelegatorSelf
41
+ # def add_back_option(menu_blocks)
42
+ # append_chrome_block(menu_blocks, MenuState::BACK)
43
+ # end
44
+
45
+ # Applies an ANSI color method to a string using a specified color key.
46
+ # The method retrieves the color method from the provided hash. If the color key
47
+ # is not present in the hash, it uses a default color method.
48
+ # @param string [String] The string to be colored.
49
+ # @param color_methods [Hash] A hash where keys are color names (String/Symbol) and values are color methods.
50
+ # @param color_key [String, Symbol] The key representing the desired color method in the color_methods hash.
51
+ # @param default_method [String] (optional) Default color method to use if color_key is not found in color_methods. Defaults to 'plain'.
52
+ # @return [String] The colored string.
53
+ def apply_color_from_hash(string, color_methods, color_key, default_method: 'plain')
54
+ color_method = color_methods.fetch(color_key, default_method).to_sym
55
+ string.to_s.send(color_method)
56
+ end
57
+
58
+ # # Enhanced `apply_color_from_hash` method to support dynamic color transformations
59
+ # # @param string [String] The string to be colored.
60
+ # # @param color_transformations [Hash] A hash mapping color names to lambdas that apply color transformations.
61
+ # # @param color_key [String, Symbol] The key representing the desired color transformation in the color_transformations hash.
62
+ # # @param default_transformation [Proc] Default color transformation to use if color_key is not found in color_transformations.
63
+ # # @return [String] The colored string.
64
+ # def apply_color_from_hash(string, color_transformations, color_key, default_transformation: ->(str) { str })
65
+ # transformation = color_transformations.fetch(color_key.to_sym, default_transformation)
66
+ # transformation.call(string)
67
+ # end
68
+ # color_transformations = {
69
+ # red: ->(str) { "\e[31m#{str}\e[0m" }, # ANSI color code for red
70
+ # green: ->(str) { "\e[32m#{str}\e[0m" }, # ANSI color code for green
71
+ # # Add more color transformations as needed
72
+ # }
73
+ # string = "Hello, World!"
74
+ # colored_string = apply_color_from_hash(string, color_transformations, :red)
75
+ # puts colored_string # This will print the string in red
76
+
77
+ # Searches for the first element in a collection where the specified key matches a given value.
78
+ # This method is particularly useful for finding a specific hash-like object within an enumerable collection.
79
+ # If no match is found, it returns a specified default value.
80
+ #
81
+ # @param blocks [Enumerable] The collection of hash-like objects to search.
82
+ # @param key [Object] The key to search for in each element of the collection.
83
+ # @param value [Object] The value to match against each element's corresponding key value.
84
+ # @param default [Object, nil] The default value to return if no match is found (optional).
85
+ # @return [Object, nil] The first matching element or the default value if no match is found.
86
+ def block_find(blocks, key, value, default = nil)
87
+ blocks.find { |item| item[key] == value } || default
88
+ end
89
+
90
+ def code_merge(*bodies)
91
+ merge_lists(*bodies)
92
+ end
93
+
94
+ def count_matches_in_lines(lines, regex)
95
+ lines.count { |line| line.to_s.match(regex) }
96
+ end
97
+
98
+ def create_directory_for_file(file_path)
99
+ FileUtils.mkdir_p(File.dirname(file_path))
100
+ end
101
+
102
+ # Creates a file at the specified path, writes the given content to it,
103
+ # and sets file permissions if required. Handles any errors encountered during the process.
104
+ #
105
+ # @param file_path [String] The path where the file will be created.
106
+ # @param content [String] The content to write into the file.
107
+ # @param chmod_value [Integer] The file permission value to set; skips if zero.
108
+ def create_file_and_write_string_with_permissions(file_path, content,
109
+ chmod_value)
110
+ create_directory_for_file(file_path)
111
+ File.write(file_path, content)
112
+ set_file_permissions(file_path, chmod_value) unless chmod_value.zero?
113
+ rescue StandardError
114
+ error_handler('create_file_and_write_string_with_permissions')
115
+ end
116
+
117
+ # def create_temp_file
118
+ # Dir::Tmpname.create(self.class.to_s) { |path| path }
119
+ # end
120
+
121
+ # Updates the title of an FCB object from its body content if the title is nil or empty.
122
+ def default_block_title_from_body(fcb)
123
+ return unless fcb.title.nil? || fcb.title.empty?
124
+
125
+ fcb.derive_title_from_body
126
+ end
127
+
128
+ # delete the current line if it is empty and the previous is also empty
129
+ def delete_consecutive_blank_lines!(blocks_menu)
130
+ blocks_menu.process_and_conditionally_delete! do |prev_item, current_item, _next_item|
131
+ prev_item&.fetch(:chrome, nil) && !prev_item&.fetch(:oname).present? &&
132
+ current_item&.fetch(:chrome, nil) && !current_item&.fetch(:oname).present?
133
+ end
134
+ end
135
+
136
+ # # Deletes a temporary file specified by an environment variable.
137
+ # # Checks if the file exists before attempting to delete it and clears the environment variable afterward.
138
+ # # Any errors encountered during deletion are handled gracefully.
139
+ # def delete_required_temp_file(temp_blocks_file_path)
140
+ # return if temp_blocks_file_path.nil? || temp_blocks_file_path.empty?
141
+
142
+ # HashDelegator.remove_file_without_standard_errors(temp_blocks_file_path)
143
+ # end
144
+
145
+ def error_handler(name = '', opts = {})
146
+ Exceptions.error_handler(
147
+ "HashDelegator.#{name} -- #{$!}",
148
+ opts
149
+ )
150
+ end
151
+
152
+ # # DebugHelper.d ["HDmm method_name: #{method_name}", "#{first_n_caller_items 1}"]
153
+ # def first_n_caller_items(n)
154
+ # call_stack = caller
155
+ # base_path = File.realpath('.')
156
+
157
+ # # Modify the call stack to remove the base path and keep only the first n items
158
+ # call_stack.take(n + 1)[1..].map do |line|
159
+ # " . #{line.sub(/^#{Regexp.escape(base_path)}\//, '')}"
160
+ # end.join("\n")
161
+ # end
162
+
163
+ # Formats and returns the execution streams (like stdin, stdout, stderr) for a given key.
164
+ # It concatenates the array of strings found under the specified key in the run_state's files.
165
+ #
166
+ # @param key [Symbol] The key corresponding to the desired execution stream.
167
+ # @return [String] A concatenated string of the execution stream's contents.
168
+ def format_execution_streams(key, files = {})
169
+ (files || {}).fetch(key, []).join
170
+ end
171
+
172
+ # Indents all lines in a given string with a specified indentation string.
173
+ # @param body [String] A multi-line string to be indented.
174
+ # @param indent [String] The string used for indentation (default is an empty string).
175
+ # @return [String] A single string with each line indented as specified.
176
+ def indent_all_lines(body, indent = nil)
177
+ return body unless indent&.non_empty?
178
+
179
+ body.lines.map { |line| indent + line.chomp }.join("\n")
180
+ end
181
+
182
+ def initialize_fcb_names(fcb)
183
+ fcb.oname = fcb.dname = fcb.title || ''
184
+ end
185
+
186
+ def merge_lists(*args)
187
+ # Filters out nil values, flattens the arrays, and ensures an empty list is returned if no valid lists are provided
188
+ merged = args.compact.flatten
189
+ merged.empty? ? [] : merged
190
+ end
191
+
192
+ def next_link_state(block_name_from_cli, was_using_cli, block_state)
193
+ # &bsp 'next_link_state', block_name_from_cli, was_using_cli, block_state
194
+ # Set block_name based on block_name_from_cli
195
+ block_name = block_name_from_cli ? @cli_block_name : nil
196
+ # &bsp 'block_name:', block_name
197
+
198
+ # Determine the state of breaker based on was_using_cli and the block type
199
+ breaker = !block_name && !block_name_from_cli && was_using_cli && block_state.block[:shell] == BlockType::BASH
200
+ # &bsp 'breaker:', breaker
201
+
202
+ # Reset block_name_from_cli if the conditions are not met
203
+ block_name_from_cli ||= false
204
+ # &bsp 'block_name_from_cli:', block_name_from_cli
205
+
206
+ [block_name, block_name_from_cli, breaker]
207
+ end
208
+
209
+ def parse_yaml_data_from_body(body)
210
+ body.any? ? YAML.load(body.join("\n")) : {}
211
+ end
212
+
213
+ # Reads required code blocks from a temporary file specified by an environment variable.
214
+ # @return [Array<String>] Lines read from the temporary file, or an empty array if file is not found or path is empty.
215
+ def read_required_blocks_from_temp_file(temp_blocks_file_path)
216
+ return [] if temp_blocks_file_path.to_s.empty?
217
+
218
+ if File.exist?(temp_blocks_file_path)
219
+ File.readlines(
220
+ temp_blocks_file_path, chomp: true
221
+ )
222
+ else
223
+ []
224
+ end
225
+ end
226
+
227
+ def remove_file_without_standard_errors(path)
228
+ FileUtils.rm_f(path)
229
+ end
230
+
231
+ # Evaluates the given string as Ruby code and rescues any StandardErrors.
232
+ # If an error occurs, it calls the error_handler method with 'safeval'.
233
+ # @param str [String] The string to be evaluated.
234
+ # @return [Object] The result of evaluating the string.
235
+ def safeval(str)
236
+ eval(str)
237
+ rescue StandardError # catches NameError, StandardError
238
+ error_handler('safeval')
239
+ end
240
+
241
+ def set_file_permissions(file_path, chmod_value)
242
+ File.chmod(chmod_value, file_path)
243
+ end
244
+
245
+ # Creates a TTY prompt with custom settings. Specifically, it disables the default 'cross' symbol and
246
+ # defines a lambda function to handle interrupts.
247
+ # @return [TTY::Prompt] A new TTY::Prompt instance with specified configurations.
248
+ def tty_prompt_without_disabled_symbol
249
+ TTY::Prompt.new(
250
+ interrupt: lambda {
251
+ puts
252
+ raise TTY::Reader::InputInterrupt
253
+ },
254
+ symbols: { cross: ' ' }
255
+ )
256
+ end
257
+
258
+ # Updates the attributes of the given fcb object and conditionally yields to a block.
259
+ # It initializes fcb names and sets the default block title from fcb's body.
260
+ # If the fcb has a body and meets certain conditions, it yields to the given block.
261
+ #
262
+ # @param fcb [Object] The fcb object whose attributes are to be updated.
263
+ # @param selected_messages [Array<Symbol>] A list of message types to determine if yielding is applicable.
264
+ # @param block [Block] An optional block to yield to if conditions are met.
265
+ def update_menu_attrib_yield_selected(fcb, selected_messages, configuration = {}, &block)
266
+ initialize_fcb_names(fcb)
267
+ return unless fcb.body
268
+
269
+ default_block_title_from_body(fcb)
270
+ MarkdownExec::Filter.yield_to_block_if_applicable(fcb, selected_messages, configuration,
271
+ &block)
272
+ end
273
+
274
+ # Writes the provided code blocks to a file.
275
+ # @param code_blocks [String] Code blocks to write into the file.
276
+ def write_code_to_file(content, path)
277
+ File.write(path, content)
278
+ end
279
+
280
+ def write_execution_output_to_file(files, filespec)
281
+ FileUtils.mkdir_p File.dirname(filespec)
282
+
283
+ File.write(
284
+ filespec,
285
+ ["-STDOUT-\n",
286
+ format_execution_streams(ExecutionStreams::StdOut, files),
287
+ "-STDERR-\n",
288
+ format_execution_streams(ExecutionStreams::StdErr, files),
289
+ "-STDIN-\n",
290
+ format_execution_streams(ExecutionStreams::StdIn, files),
291
+ "\n"].join
292
+ )
293
+ end
294
+
295
+ # Yields a line as a new block if the selected message type includes :line.
296
+ # @param [String] line The line to be processed.
297
+ # @param [Array<Symbol>] selected_messages A list of message types to check.
298
+ # @param [Proc] block The block to be called with the line data.
299
+ def yield_line_if_selected(line, selected_messages, &block)
300
+ return unless block && selected_messages.include?(:line)
301
+
302
+ block.call(:line, MarkdownExec::FCB.new(body: [line]))
303
+ end
304
+ end
305
+ ### require_relative 'hash_delegator_self'
306
+
40
307
  # This module provides methods for compacting and converting data structures.
41
308
  module CompactionHelpers
42
309
  # Converts an array of key-value pairs into a hash, applying compaction to the values.
@@ -106,11 +373,12 @@ module MarkdownExec
106
373
  class HashDelegator
107
374
  attr_accessor :most_recent_loaded_filename, :pass_args, :run_state
108
375
 
376
+ extend HashDelegatorSelf
109
377
  include CompactionHelpers
110
378
 
111
379
  def initialize(delegate_object = {})
112
380
  @delegate_object = delegate_object
113
- @prompt = tty_prompt_without_disabled_symbol
381
+ @prompt = HashDelegator.tty_prompt_without_disabled_symbol
114
382
 
115
383
  @most_recent_loaded_filename = nil
116
384
  @pass_args = []
@@ -138,11 +406,20 @@ module MarkdownExec
138
406
  # along with initial and final dividers, based on the delegate object's configuration.
139
407
  #
140
408
  # @param menu_blocks [Array] The array of menu block elements to be modified.
141
- def add_menu_chrome_blocks!(menu_blocks)
409
+ def add_menu_chrome_blocks!(menu_blocks, link_state)
142
410
  return unless @delegate_object[:menu_link_format].present?
143
411
 
412
+ if @delegate_object[:menu_with_inherited_lines]
413
+ add_inherited_lines(menu_blocks,
414
+ link_state)
415
+ end
416
+
417
+ # back before exit
144
418
  add_back_option(menu_blocks) if should_add_back_option?
419
+
420
+ # exit after other options
145
421
  add_exit_option(menu_blocks) if @delegate_object[:menu_with_exit]
422
+
146
423
  add_dividers(menu_blocks)
147
424
  end
148
425
 
@@ -152,13 +429,17 @@ module MarkdownExec
152
429
  append_chrome_block(menu_blocks, MenuState::BACK)
153
430
  end
154
431
 
432
+ def add_dividers(menu_blocks)
433
+ append_divider(menu_blocks, :initial)
434
+ append_divider(menu_blocks, :final)
435
+ end
436
+
155
437
  def add_exit_option(menu_blocks)
156
438
  append_chrome_block(menu_blocks, MenuState::EXIT)
157
439
  end
158
440
 
159
- def add_dividers(menu_blocks)
160
- append_divider(menu_blocks, :initial)
161
- append_divider(menu_blocks, :final)
441
+ def add_inherited_lines(menu_blocks, link_state)
442
+ append_inherited_lines(menu_blocks, link_state)
162
443
  end
163
444
 
164
445
  public
@@ -179,7 +460,7 @@ module MarkdownExec
179
460
  end
180
461
 
181
462
  formatted_name = format(@delegate_object[:menu_link_format],
182
- safeval(option_name))
463
+ HashDelegator.safeval(option_name))
183
464
  chrome_block = FCB.new(
184
465
  chrome: true,
185
466
  dname: HashDelegator.new(@delegate_object).string_send_color(
@@ -195,6 +476,39 @@ module MarkdownExec
195
476
  end
196
477
  end
197
478
 
479
+ # Appends a formatted divider to the specified position in a menu block array.
480
+ # The method checks for the presence of formatting options before appending.
481
+ #
482
+ # @param menu_blocks [Array] The array of menu block elements.
483
+ # @param position [Symbol] The position to insert the divider (:initial or :final).
484
+ def append_inherited_lines(menu_blocks, link_state, position: top)
485
+ return unless link_state.inherited_lines.present?
486
+
487
+ insert_at_top = @delegate_object[:menu_inherited_lines_at_top]
488
+ chrome_blocks = link_state.inherited_lines.map do |line|
489
+ formatted = format(@delegate_object[:menu_inherited_lines_format],
490
+ { line: line })
491
+ FCB.new(
492
+ chrome: true,
493
+ disabled: '',
494
+ dname: HashDelegator.new(@delegate_object).string_send_color(
495
+ formatted, :menu_inherited_lines_color
496
+ ),
497
+ oname: formatted
498
+ )
499
+ end
500
+
501
+ if insert_at_top
502
+ # Prepend an array of elements to the beginning
503
+ menu_blocks.unshift(*chrome_blocks)
504
+ else
505
+ # Append an array of elements to the end
506
+ menu_blocks.concat(chrome_blocks)
507
+ end
508
+ rescue StandardError
509
+ HashDelegator.error_handler('append_inherited_lines')
510
+ end
511
+
198
512
  # Appends a formatted divider to the specified position in a menu block array.
199
513
  # The method checks for the presence of formatting options before appending.
200
514
  #
@@ -224,19 +538,6 @@ module MarkdownExec
224
538
 
225
539
  # private
226
540
 
227
- # Searches for the first element in a collection where the specified key matches a given value.
228
- # This method is particularly useful for finding a specific hash-like object within an enumerable collection.
229
- # If no match is found, it returns a specified default value.
230
- #
231
- # @param blocks [Enumerable] The collection of hash-like objects to search.
232
- # @param key [Object] The key to search for in each element of the collection.
233
- # @param value [Object] The value to match against each element's corresponding key value.
234
- # @param default [Object, nil] The default value to return if no match is found (optional).
235
- # @return [Object, nil] The first matching element or the default value if no match is found.
236
- def block_find(blocks, key, value, default = nil)
237
- blocks.find { |item| item[key] == value } || default
238
- end
239
-
240
541
  # Iterates through nested files to collect various types of blocks, including dividers, tasks, and others.
241
542
  # The method categorizes blocks based on their type and processes them accordingly.
242
543
  #
@@ -246,9 +547,10 @@ module MarkdownExec
246
547
  iter_blocks_from_nested_files do |btype, fcb|
247
548
  process_block_based_on_type(blocks, btype, fcb)
248
549
  end
550
+ # &bc 'blocks.count:', blocks.count
249
551
  blocks
250
552
  rescue StandardError
251
- error_handler('blocks_from_nested_files')
553
+ HashDelegator.error_handler('blocks_from_nested_files')
252
554
  end
253
555
 
254
556
  # private
@@ -273,15 +575,6 @@ module MarkdownExec
273
575
  true
274
576
  end
275
577
 
276
- def code_join(*bodies)
277
- bc = bodies&.compact
278
- bc.count.positive? ? bc.join("\n") : nil
279
- end
280
-
281
- def code_merge(*bodies)
282
- merge_lists(*bodies)
283
- end
284
-
285
578
  # Collects required code lines based on the selected block and the delegate object's configuration.
286
579
  # If the block type is VARS, it also sets environment variables based on the block's content.
287
580
  #
@@ -312,7 +605,7 @@ module MarkdownExec
312
605
  highlight: [@delegate_object[:block_name]])
313
606
  end
314
607
 
315
- code_merge link_state&.inherited_lines, required[:code]
608
+ HashDelegator.code_merge(link_state&.inherited_lines, required[:code])
316
609
  end
317
610
 
318
611
  def command_execute(command, args: [])
@@ -359,21 +652,21 @@ module MarkdownExec
359
652
  @fout.fout "Error ENOENT: #{err.inspect}"
360
653
  end
361
654
 
362
- def command_or_user_selected_block(all_blocks, menu_blocks, default)
655
+ def load_cli_or_user_selected_block(all_blocks, menu_blocks, default)
363
656
  if @delegate_object[:block_name].present?
364
657
  block = all_blocks.find do |item|
365
658
  item[:oname] == @delegate_object[:block_name]
366
- end
659
+ end&.merge(block_name_from_ui: false)
367
660
  else
368
661
  block_state = wait_for_user_selected_block(all_blocks, menu_blocks,
369
662
  default)
370
- block = block_state.block
663
+ block = block_state.block&.merge(block_name_from_ui: true)
371
664
  state = block_state.state
372
665
  end
373
666
 
374
667
  SelectedBlockMenuState.new(block, state)
375
668
  rescue StandardError
376
- error_handler('command_or_user_selected_block')
669
+ HashDelegator.error_handler('load_cli_or_user_selected_block')
377
670
  end
378
671
 
379
672
  # This method is responsible for handling the execution of generic blocks in a markdown document.
@@ -383,8 +676,7 @@ module MarkdownExec
383
676
  # @param mdoc [Object] The markdown document object containing code blocks.
384
677
  # @param selected [Hash] The selected item from the menu to be executed.
385
678
  # @return [LoadFileLinkState] An object indicating whether to load the next block or reuse the current one.
386
- def compile_execute_bash_and_special_blocks_and_trigger_reuse(mdoc, selected,
387
- link_state = nil, block_source:)
679
+ def compile_execute_and_trigger_reuse(mdoc, selected, link_state = nil, block_source:)
388
680
  required_lines = collect_required_code_lines(mdoc, selected, link_state,
389
681
  block_source: block_source)
390
682
  output_or_approval = @delegate_object[:output_script] || @delegate_object[:user_must_approve]
@@ -413,17 +705,9 @@ module MarkdownExec
413
705
  def count_blocks_in_filename
414
706
  regex = Regexp.new(@delegate_object[:fenced_start_and_end_regex])
415
707
  lines = cfile.readlines(@delegate_object[:filename])
416
- count_matches_in_lines(lines, regex) / 2
417
- end
418
-
419
- # private
420
-
421
- def count_matches_in_lines(lines, regex)
422
- lines.count { |line| line.to_s.match(regex) }
708
+ HashDelegator.count_matches_in_lines(lines, regex) / 2
423
709
  end
424
710
 
425
- # private
426
-
427
711
  ##
428
712
  # Creates and adds a formatted block to the blocks array based on the provided match and format options.
429
713
  # @param blocks [Array] The array of blocks to add the new block to.
@@ -450,12 +734,13 @@ module MarkdownExec
450
734
  # @param use_chrome [Boolean] Indicates if the chrome styling should be applied.
451
735
  def create_and_add_chrome_blocks(blocks, fcb)
452
736
  match_criteria = [
453
- { match: :menu_task_match, format: :menu_task_format,
454
- color: :menu_task_color },
737
+ { match: :heading1_match, format: :menu_heading1_format, color: :menu_heading1_color },
738
+ { match: :heading2_match, format: :menu_heading2_format, color: :menu_heading2_color },
739
+ { match: :heading3_match, format: :menu_heading3_format, color: :menu_heading3_color },
455
740
  { match: :menu_divider_match, format: :menu_divider_format,
456
741
  color: :menu_divider_color },
457
- { match: :menu_note_match, format: :menu_note_format,
458
- color: :menu_note_color }
742
+ { match: :menu_note_match, format: :menu_note_format, color: :menu_note_color },
743
+ { match: :menu_task_match, format: :menu_task_format, color: :menu_task_color }
459
744
  ]
460
745
  match_criteria.each do |criteria|
461
746
  unless @delegate_object[criteria[:match]].present? &&
@@ -469,31 +754,10 @@ module MarkdownExec
469
754
  end
470
755
  end
471
756
 
472
- # Creates a file at the specified path, writes the given content to it,
473
- # and sets file permissions if required. Handles any errors encountered during the process.
474
- #
475
- # @param file_path [String] The path where the file will be created.
476
- # @param content [String] The content to write into the file.
477
- # @param chmod_value [Integer] The file permission value to set; skips if zero.
478
- def create_file_and_write_string_with_permissions(file_path, content,
479
- chmod_value)
480
- create_directory_for_file(file_path)
481
- File.write(file_path, content)
482
- set_file_permissions(file_path, chmod_value) unless chmod_value.zero?
483
- rescue StandardError
484
- error_handler('create_file_and_write_string_with_permissions')
485
- end
486
-
487
- # private
488
-
489
- def create_directory_for_file(file_path)
490
- FileUtils.mkdir_p(File.dirname(file_path))
491
- end
492
-
493
757
  def create_divider(position)
494
758
  divider_key = position == :initial ? :menu_initial_divider : :menu_final_divider
495
759
  oname = format(@delegate_object[:menu_divider_format],
496
- safeval(@delegate_object[divider_key]))
760
+ HashDelegator.safeval(@delegate_object[divider_key]))
497
761
 
498
762
  FCB.new(
499
763
  chrome: true,
@@ -503,38 +767,6 @@ module MarkdownExec
503
767
  )
504
768
  end
505
769
 
506
- # private
507
-
508
- def create_temp_file
509
- Dir::Tmpname.create(self.class.to_s) { |path| path }
510
- end
511
-
512
- # Updates the title of an FCB object from its body content if the title is nil or empty.
513
- def default_block_title_from_body(fcb)
514
- return unless fcb.title.nil? || fcb.title.empty?
515
-
516
- fcb.derive_title_from_body
517
- end
518
-
519
- # delete the current line if it is empty and the previous is also empty
520
- def delete_consecutive_blank_lines!(blocks_menu)
521
- blocks_menu.process_and_conditionally_delete! do |prev_item, current_item, _next_item|
522
- prev_item&.fetch(:chrome, nil) && !prev_item&.fetch(:oname).present? &&
523
- current_item&.fetch(:chrome, nil) && !current_item&.fetch(:oname).present?
524
- end
525
- end
526
-
527
- # Deletes a temporary file specified by an environment variable.
528
- # Checks if the file exists before attempting to delete it and clears the environment variable afterward.
529
- # Any errors encountered during deletion are handled gracefully.
530
- def delete_required_temp_file(temp_blocks_file_path)
531
- return if temp_blocks_file_path.nil? || temp_blocks_file_path.empty?
532
-
533
- safely_remove_file(temp_blocks_file_path)
534
- rescue StandardError
535
- error_handler('delete_required_temp_file')
536
- end
537
-
538
770
  # Determines the state of a selected block in the menu based on the selected option.
539
771
  # It categorizes the selected option into either EXIT, BACK, or CONTINUE state.
540
772
  #
@@ -571,15 +803,6 @@ module MarkdownExec
571
803
  @delegate_object[:menu_divider_format].present? && @delegate_object[divider_key].present?
572
804
  end
573
805
 
574
- def error_handler(name = '', opts = {})
575
- Exceptions.error_handler(
576
- "HashDelegator.#{name} -- #{$!}",
577
- opts
578
- )
579
- end
580
-
581
- # public
582
-
583
806
  # Executes a block of code that has been approved for execution.
584
807
  # It sets the script block name, writes command files if required, and handles the execution
585
808
  # including output formatting and summarization.
@@ -587,8 +810,8 @@ module MarkdownExec
587
810
  # @param required_lines [Array<String>] The lines of code to be executed.
588
811
  # @param selected [FCB] The selected functional code block object.
589
812
  def execute_required_lines(required_lines = [])
590
- # set_script_block_name(selected)
591
- save_executed_script_if_specified(required_lines)
813
+ # @run_state.script_block_name = selected[:oname]
814
+ write_command_file(required_lines) if @delegate_object[:save_executed_script]
592
815
  format_and_execute_command(required_lines)
593
816
  post_execution_process
594
817
  end
@@ -602,8 +825,8 @@ module MarkdownExec
602
825
  # @param opts [Hash] Options hash containing configuration settings.
603
826
  # @param mdoc [YourMDocClass] An instance of the MDoc class.
604
827
  #
605
- def execute_bash_and_special_blocks(selected, mdoc, link_state = LinkState.new,
606
- block_source:)
828
+ def execute_shell_type(selected, mdoc, link_state = LinkState.new,
829
+ block_source:)
607
830
  if selected.fetch(:shell, '') == BlockType::LINK
608
831
  push_link_history_and_trigger_load(selected.fetch(:body, ''), mdoc, selected,
609
832
  link_state)
@@ -618,8 +841,8 @@ module MarkdownExec
618
841
  options_state.load_file_link_state
619
842
 
620
843
  else
621
- compile_execute_bash_and_special_blocks_and_trigger_reuse(mdoc, selected, link_state,
622
- block_source: block_source)
844
+ compile_execute_and_trigger_reuse(mdoc, selected, link_state,
845
+ block_source: block_source)
623
846
  end
624
847
  end
625
848
 
@@ -637,17 +860,6 @@ module MarkdownExec
637
860
  string_send_color(data_string, color_sym)
638
861
  end
639
862
 
640
- # DebugHelper.d ["HDmm method_name: #{method_name}", "#{first_n_caller_items 1}"]
641
- def first_n_caller_items(n)
642
- call_stack = caller
643
- base_path = File.realpath('.')
644
-
645
- # Modify the call stack to remove the base path and keep only the first n items
646
- call_stack.take(n + 1)[1..].map do |line|
647
- " . #{line.sub(/^#{Regexp.escape(base_path)}\//, '')}"
648
- end.join("\n")
649
- end
650
-
651
863
  def format_and_execute_command(lines)
652
864
  formatted_command = lines.flatten.join("\n")
653
865
  @fout.fout fetch_color(data_sym: :script_execution_head,
@@ -673,16 +885,6 @@ module MarkdownExec
673
885
  string_send_color(formatted_string, color_sym)
674
886
  end
675
887
 
676
- # Formats and returns the execution streams (like stdin, stdout, stderr) for a given key.
677
- # It concatenates the array of strings found under the specified key in the run_state's files.
678
- #
679
- # @param key [Symbol] The key corresponding to the desired execution stream.
680
- # @return [String] A concatenated string of the execution stream's contents.
681
- def format_execution_streams(key)
682
- files = @run_state.files || {}
683
- files.fetch(key, []).join
684
- end
685
-
686
888
  # Processes a block to generate its summary, modifying its attributes based on various matching criteria.
687
889
  # It handles special formatting for bash blocks, extracting and setting properties like call, stdin, stdout, and dname.
688
890
  #
@@ -712,7 +914,7 @@ module MarkdownExec
712
914
  # It sets the block name and determines if the user clicked the back link in the menu.
713
915
  #
714
916
  # @param block_state [Object] An object representing the state of a block in the menu.
715
- def handle_block_state(block_state)
917
+ def handle_back_or_continue(block_state)
716
918
  return if block_state.nil?
717
919
  unless [MenuState::BACK,
718
920
  MenuState::CONTINUE].include?(block_state.state)
@@ -745,16 +947,6 @@ module MarkdownExec
745
947
  end
746
948
  end
747
949
 
748
- # Indents all lines in a given string with a specified indentation string.
749
- # @param body [String] A multi-line string to be indented.
750
- # @param indent [String] The string used for indentation (default is an empty string).
751
- # @return [String] A single string with each line indented as specified.
752
- def indent_all_lines(body, indent = nil)
753
- return body unless indent&.non_empty?
754
-
755
- body.lines.map { |line| indent + line.chomp }.join("\n")
756
- end
757
-
758
950
  # Initializes variables for regex and other states
759
951
  def initial_state
760
952
  {
@@ -785,11 +977,8 @@ module MarkdownExec
785
977
  File.join @delegate_object[:saved_stdout_folder],
786
978
  @delegate_object[:logged_stdout_filename]
787
979
  @logged_stdout_filespec = @delegate_object[:logged_stdout_filespec]
788
- write_execution_output_to_file
789
- end
790
-
791
- def initialize_fcb_names(fcb)
792
- fcb.oname = fcb.dname = fcb.title || ''
980
+ HashDelegator.write_execution_output_to_file(@run_state.files,
981
+ @delegate_object[:logged_stdout_filespec])
793
982
  end
794
983
 
795
984
  # Iterates through blocks in a file, applying the provided block to each line.
@@ -846,7 +1035,7 @@ module MarkdownExec
846
1035
  return
847
1036
  end
848
1037
 
849
- block = block_find(all_blocks, :oname, block_name)
1038
+ block = HashDelegator.block_find(all_blocks, :oname, block_name)
850
1039
  return unless block
851
1040
 
852
1041
  options_state = read_show_options_and_trigger_reuse(block)
@@ -867,7 +1056,7 @@ module MarkdownExec
867
1056
 
868
1057
  ## Handles the file loading and returns the blocks in the file and MDoc instance
869
1058
  #
870
- def mdoc_menu_and_blocks_from_nested_files
1059
+ def mdoc_menu_and_blocks_from_nested_files(link_state)
871
1060
  all_blocks, mdoc = mdoc_and_blocks_from_nested_files
872
1061
 
873
1062
  # recreate menu with new options
@@ -875,8 +1064,9 @@ module MarkdownExec
875
1064
  all_blocks, mdoc = mdoc_and_blocks_from_nested_files if load_auto_blocks(all_blocks)
876
1065
 
877
1066
  menu_blocks = mdoc.fcbs_per_options(@delegate_object)
878
- add_menu_chrome_blocks!(menu_blocks)
879
- delete_consecutive_blank_lines!(menu_blocks) if true ### compress empty lines
1067
+ add_menu_chrome_blocks!(menu_blocks, link_state)
1068
+ ### compress empty lines
1069
+ HashDelegator.delete_consecutive_blank_lines!(menu_blocks) if true
880
1070
  [all_blocks, menu_blocks, mdoc]
881
1071
  end
882
1072
 
@@ -895,7 +1085,7 @@ module MarkdownExec
895
1085
  # @param option_symbol [Symbol] The symbol key for the menu option in the delegate object.
896
1086
  # @return [String] The formatted or original value of the menu option.
897
1087
  def menu_chrome_formatted_option(option_symbol = :menu_option_back_name)
898
- option_value = safeval(@delegate_object.fetch(option_symbol, ''))
1088
+ option_value = HashDelegator.safeval(@delegate_object.fetch(option_symbol, ''))
899
1089
 
900
1090
  if @delegate_object[:menu_chrome_format]
901
1091
  format(@delegate_object[:menu_chrome_format], option_value)
@@ -904,12 +1094,6 @@ module MarkdownExec
904
1094
  end
905
1095
  end
906
1096
 
907
- def merge_lists(*args)
908
- # Filters out nil values, flattens the arrays, and ensures an empty list is returned if no valid lists are provided
909
- merged = args.compact.flatten
910
- merged.empty? ? [] : merged
911
- end
912
-
913
1097
  # If a method is missing, treat it as a key for the @delegate_object.
914
1098
  def method_missing(method_name, *args, &block)
915
1099
  if @delegate_object.respond_to?(method_name)
@@ -922,15 +1106,13 @@ module MarkdownExec
922
1106
  end
923
1107
  end
924
1108
 
925
- def pop_cli_argument!
926
- return false unless @delegate_object[:input_cli_rest].present?
1109
+ def shift_cli_argument
1110
+ return true unless @menu_base_options[:input_cli_rest].present?
927
1111
 
928
- @cli_block_name = @delegate_object[:input_cli_rest].pop
929
- true
1112
+ @cli_block_name = @menu_base_options[:input_cli_rest].shift
1113
+ false
930
1114
  end
931
1115
 
932
- # private
933
-
934
1116
  def output_color_formatted(data_sym, color_sym)
935
1117
  formatted_string = string_send_color(@delegate_object[data_sym],
936
1118
  color_sym)
@@ -983,16 +1165,10 @@ module MarkdownExec
983
1165
  ), level: level
984
1166
  end
985
1167
 
986
- # private
987
-
988
- def parse_yaml_data_from_body(body)
989
- body.any? ? YAML.load(body.join("\n")) : {}
990
- end
991
-
992
1168
  def pop_add_current_code_to_head_and_trigger_load(_link_state, block_names, code_lines,
993
1169
  dependencies)
994
1170
  pop = @link_history.pop # updatable
995
- next_link_state = LinkState.new(
1171
+ next_state = LinkState.new(
996
1172
  block_name: pop.block_name,
997
1173
  document_filename: pop.document_filename,
998
1174
  inherited_block_names:
@@ -1000,12 +1176,12 @@ module MarkdownExec
1000
1176
  inherited_dependencies:
1001
1177
  dependencies.merge(pop.inherited_dependencies || {}), ### merge, not replace, key data
1002
1178
  inherited_lines:
1003
- code_merge(pop.inherited_lines, code_lines)
1179
+ HashDelegator.code_merge(pop.inherited_lines, code_lines)
1004
1180
  )
1005
- @link_history.push(next_link_state)
1181
+ @link_history.push(next_state)
1006
1182
 
1007
- next_link_state.block_name = nil
1008
- LoadFileLinkState.new(LoadFile::Load, next_link_state)
1183
+ next_state.block_name = nil
1184
+ LoadFileLinkState.new(LoadFile::Load, next_state)
1009
1185
  end
1010
1186
 
1011
1187
  # This method handles the back-link operation in the Markdown execution context.
@@ -1040,7 +1216,7 @@ module MarkdownExec
1040
1216
  %i[block_name_include_match block_name_wrapper_match])
1041
1217
 
1042
1218
  fcb.merge!(
1043
- name: indent_all_lines(fcb.dname, fcb.fetch(:indent, nil)),
1219
+ name: HashDelegator.indent_all_lines(fcb.dname, fcb.fetch(:indent, nil)),
1044
1220
  label: BlockLabel.make(
1045
1221
  body: fcb[:body],
1046
1222
  filename: @delegate_object[:filename],
@@ -1070,10 +1246,7 @@ module MarkdownExec
1070
1246
  when :filter
1071
1247
  %i[blocks line]
1072
1248
  when :line
1073
- unless @delegate_object[:no_chrome]
1074
- create_and_add_chrome_blocks(blocks,
1075
- fcb)
1076
- end
1249
+ create_and_add_chrome_blocks(blocks, fcb) unless @delegate_object[:no_chrome]
1077
1250
  end
1078
1251
  end
1079
1252
 
@@ -1147,7 +1320,7 @@ module MarkdownExec
1147
1320
  # @return [LoadFileLinkState] Object indicating the next action for file loading.
1148
1321
  def push_link_history_and_trigger_load(link_block_body, mdoc, selected,
1149
1322
  link_state = LinkState.new)
1150
- link_block_data = parse_yaml_data_from_body(link_block_body)
1323
+ link_block_data = HashDelegator.parse_yaml_data_from_body(link_block_body)
1151
1324
 
1152
1325
  # load key and values from link block into current environment
1153
1326
  #
@@ -1177,7 +1350,7 @@ module MarkdownExec
1177
1350
  # if an eval link block, evaluate code_lines and return its standard output
1178
1351
  #
1179
1352
  if link_block_data.fetch('eval', false)
1180
- all_code = code_merge(link_state&.inherited_lines, code_lines)
1353
+ all_code = HashDelegator.code_merge(link_state&.inherited_lines, code_lines)
1181
1354
  output = `#{all_code.join("\n")}`.split("\n")
1182
1355
  label_format_above = @delegate_object[:shell_code_label_format_above]
1183
1356
  label_format_below = @delegate_object[:shell_code_label_format_below]
@@ -1206,7 +1379,7 @@ module MarkdownExec
1206
1379
  curr_document_filename: @delegate_object[:filename],
1207
1380
  inherited_block_names: ((link_state&.inherited_block_names || []) + block_names).sort.uniq,
1208
1381
  inherited_dependencies: (link_state&.inherited_dependencies || {}).merge(dependencies || {}), ### merge, not replace, key data
1209
- inherited_lines: code_merge(link_state&.inherited_lines, code_lines),
1382
+ inherited_lines: HashDelegator.code_merge(link_state&.inherited_lines, code_lines),
1210
1383
  next_block_name: link_block_data['block'] || '',
1211
1384
  next_document_filename: next_document_filename,
1212
1385
  next_load_file: next_document_filename == @delegate_object[:filename] ? LoadFile::Reuse : LoadFile::Load
@@ -1214,20 +1387,6 @@ module MarkdownExec
1214
1387
  end
1215
1388
  end
1216
1389
 
1217
- # Reads required code blocks from a temporary file specified by an environment variable.
1218
- # @return [Array<String>] Lines read from the temporary file, or an empty array if file is not found or path is empty.
1219
- def read_required_blocks_from_temp_file(temp_blocks_file_path)
1220
- return [] if temp_blocks_file_path.to_s.empty?
1221
-
1222
- if File.exist?(temp_blocks_file_path)
1223
- File.readlines(
1224
- temp_blocks_file_path, chomp: true
1225
- )
1226
- else
1227
- []
1228
- end
1229
- end
1230
-
1231
1390
  def runtime_exception(exception_sym, name, items)
1232
1391
  if @delegate_object[exception_sym] != 0
1233
1392
  data = { name: name, detail: items.join(', ') }
@@ -1247,20 +1406,6 @@ module MarkdownExec
1247
1406
  exit @delegate_object[exception_sym]
1248
1407
  end
1249
1408
 
1250
- def safely_remove_file(path)
1251
- FileUtils.rm_f(path)
1252
- end
1253
-
1254
- # Evaluates the given string as Ruby code and rescues any StandardErrors.
1255
- # If an error occurs, it calls the error_handler method with 'safeval'.
1256
- # @param str [String] The string to be evaluated.
1257
- # @return [Object] The result of evaluating the string.
1258
- def safeval(str)
1259
- eval(str)
1260
- rescue StandardError
1261
- error_handler('safeval')
1262
- end
1263
-
1264
1409
  def save_to_file(required_lines)
1265
1410
  write_command_file(required_lines)
1266
1411
  @fout.fout "File saved: #{@run_state.saved_filespec}"
@@ -1272,7 +1417,7 @@ module MarkdownExec
1272
1417
  # Markdown document, obtain approval, and execute the chosen block of code.
1273
1418
  #
1274
1419
  # @return [Nil] Returns nil if no code block is selected or an error occurs.
1275
- def select_execute_bash_and_special_blocks(_execute: true)
1420
+ def document_menu_loop
1276
1421
  @menu_base_options = @delegate_object
1277
1422
  link_state = LinkState.new(
1278
1423
  block_name: @delegate_object[:block_name],
@@ -1280,94 +1425,146 @@ module MarkdownExec
1280
1425
  )
1281
1426
  block_name_from_cli = link_state.block_name.present?
1282
1427
  @cli_block_name = link_state.block_name
1283
- load_file = nil
1428
+ now_using_cli = block_name_from_cli
1284
1429
  menu_default_dname = nil
1285
1430
 
1286
1431
  loop do
1287
- loop do
1288
- @delegate_object = @menu_base_options.dup
1289
- @menu_user_clicked_back_link = false
1290
- @delegate_object[:filename] = link_state.document_filename
1291
- link_state.block_name = @delegate_object[:block_name] =
1292
- block_name_from_cli ? @cli_block_name : link_state.block_name
1293
-
1294
- # update @delegate_object and @menu_base_options in auto_load
1295
- #
1296
- blocks_in_file, menu_blocks, mdoc = mdoc_menu_and_blocks_from_nested_files
1432
+ # &bsp 'loop', block_name_from_cli, @cli_block_name
1433
+ block_name_from_cli, now_using_cli, blocks_in_file, menu_blocks, mdoc = \
1434
+ set_delobj_menu_loop_vars(block_name_from_cli, now_using_cli, link_state)
1297
1435
 
1298
- if @delegate_object[:dump_delegate_object]
1299
- warn format_and_highlight_hash(
1300
- @delegate_object,
1301
- label: '@delegate_object'
1302
- )
1303
- end
1436
+ # cli or user selection
1437
+ #
1438
+ block_state = load_cli_or_user_selected_block(blocks_in_file, menu_blocks,
1439
+ menu_default_dname)
1440
+ # &bsp 'block_name_from_cli:',block_name_from_cli
1441
+ if !block_state
1442
+ HashDelegator.error_handler('block_state missing', { abort: true })
1443
+ elsif block_state.state == MenuState::EXIT
1444
+ # &bsp 'load_cli_or_user_selected_block -> break'
1445
+ break
1446
+ end
1304
1447
 
1305
- if @delegate_object[:dump_blocks_in_file]
1306
- warn format_and_highlight_dependencies(
1307
- compact_and_index_hash(blocks_in_file),
1308
- label: 'blocks_in_file'
1309
- )
1310
- end
1311
- if @delegate_object[:dump_menu_blocks]
1312
- warn format_and_highlight_dependencies(
1313
- compact_and_index_hash(menu_blocks),
1314
- label: 'menu_blocks'
1315
- )
1316
- end
1317
- if @delegate_object[:dump_inherited_lines]
1318
- warn format_and_highlight_lines(
1319
- link_state.inherited_lines,
1320
- label: 'inherited_lines'
1321
- )
1322
- end
1448
+ dump_and_warn_block_state(block_state.block)
1449
+ link_state, menu_default_dname = exec_bash_next_state(block_state.block, mdoc,
1450
+ link_state)
1451
+ if prompt_user_exit(block_name_from_cli, block_state.block)
1452
+ # &bsp 'prompt_user_exit -> break'
1453
+ break
1454
+ end
1323
1455
 
1324
- block_state = command_or_user_selected_block(blocks_in_file,
1325
- menu_blocks, menu_default_dname)
1326
- return if block_state.state == MenuState::EXIT
1456
+ link_state.block_name, block_name_from_cli, cli_break = \
1457
+ HashDelegator.next_link_state(!shift_cli_argument, now_using_cli, block_state)
1327
1458
 
1328
- if block_state.block.nil?
1329
- warn_format('select_execute_bash_and_special_blocks', "Block not found -- #{@delegate_object[:block_name]}",
1330
- { abort: true })
1331
- # error_handler("Block not found -- #{opts[:block_name]}", { abort: true })
1332
- end
1459
+ if !block_state.block[:block_name_from_ui] && cli_break
1460
+ # &bsp '!block_name_from_ui + cli_break -> break'
1461
+ break
1462
+ end
1463
+ end
1464
+ rescue StandardError
1465
+ HashDelegator.error_handler('document_menu_loop',
1466
+ { abort: true })
1467
+ end
1333
1468
 
1334
- if @delegate_object[:dump_selected_block]
1335
- warn block_state.block.to_yaml.sub(/^(?:---\n)?/, "Block:\n")
1336
- end
1469
+ def exec_bash_next_state(block_state_block, mdoc, link_state)
1470
+ lfls = execute_shell_type(
1471
+ block_state_block,
1472
+ mdoc,
1473
+ link_state,
1474
+ block_source: { document_filename: @delegate_object[:filename] }
1475
+ )
1337
1476
 
1338
- load_file_link_state = execute_bash_and_special_blocks(
1339
- block_state.block,
1340
- mdoc,
1341
- link_state,
1342
- block_source: { document_filename: @delegate_object[:filename] }
1343
- )
1344
- load_file = load_file_link_state.load_file
1345
- link_state = load_file_link_state.link_state
1477
+ # if the same menu is being displayed, collect the display name of the selected menu item for use as the default item
1478
+ [lfls.link_state,
1479
+ lfls.load_file == LoadFile::Load ? nil : block_state_block[:dname]]
1480
+ end
1346
1481
 
1347
- # if the same menu is being displayed, collect the display name of the selected menu item for use as the default item
1348
- menu_default_dname = load_file == LoadFile::Load ? nil : block_state.block[:dname]
1482
+ def set_delobj_menu_loop_vars(block_name_from_cli, now_using_cli, link_state)
1483
+ block_name_from_cli, now_using_cli = \
1484
+ manage_cli_selection_state(block_name_from_cli, now_using_cli, link_state)
1485
+ set_delob_filename_block_name(link_state, block_name_from_cli)
1349
1486
 
1350
- # user prompt to exit if the menu will be displayed again
1351
- #
1352
- if !block_name_from_cli &&
1353
- block_state.block[:shell] == BlockType::BASH &&
1354
- @delegate_object[:pause_after_script_execution] &&
1355
- prompt_select_continue == MenuState::EXIT
1356
- return
1357
- end
1487
+ # update @delegate_object and @menu_base_options in auto_load
1488
+ #
1489
+ blocks_in_file, menu_blocks, mdoc = mdoc_menu_and_blocks_from_nested_files(link_state)
1490
+ dump_delobj(blocks_in_file, menu_blocks, link_state)
1358
1491
 
1359
- # exit current document/menu if loading next document or single block_name was specified
1360
- #
1361
- break if block_state.state == MenuState::CONTINUE && load_file == LoadFile::Load
1362
- break if block_name_from_cli
1363
- end
1364
- break if load_file == LoadFile::Reuse
1492
+ [block_name_from_cli, now_using_cli, blocks_in_file, menu_blocks, mdoc]
1493
+ end
1494
+
1495
+ # user prompt to exit if the menu will be displayed again
1496
+ #
1497
+ def prompt_user_exit(block_name_from_cli, block_state_block)
1498
+ !block_name_from_cli &&
1499
+ block_state_block[:shell] == BlockType::BASH &&
1500
+ @delegate_object[:pause_after_script_execution] &&
1501
+ prompt_select_continue == MenuState::EXIT
1502
+ end
1365
1503
 
1366
- block_name_from_cli = pop_cli_argument!
1504
+ def manage_cli_selection_state(block_name_from_cli, now_using_cli, link_state)
1505
+ if block_name_from_cli && @cli_block_name == @menu_base_options[:menu_persist_block_name]
1506
+ # &bsp 'pause cli control, allow user to select block'
1507
+ block_name_from_cli = false
1508
+ now_using_cli = false
1509
+ @menu_base_options[:block_name] = \
1510
+ @delegate_object[:block_name] = \
1511
+ link_state.block_name = \
1512
+ @cli_block_name = nil
1367
1513
  end
1368
- rescue StandardError
1369
- error_handler('select_execute_bash_and_special_blocks',
1370
- { abort: true })
1514
+
1515
+ @delegate_object = @menu_base_options.dup
1516
+ @menu_user_clicked_back_link = false
1517
+ [block_name_from_cli, now_using_cli]
1518
+ end
1519
+
1520
+ # Update the block name in the link state and delegate object.
1521
+ #
1522
+ # This method updates the block name based on whether it was specified
1523
+ # through the CLI or derived from the link state.
1524
+ #
1525
+ # @param link_state [LinkState] The current link state object.
1526
+ # @param block_name_from_cli [Boolean] Indicates if the block name is from CLI.
1527
+ def set_delob_filename_block_name(link_state, block_name_from_cli)
1528
+ @delegate_object[:filename] = link_state.document_filename
1529
+ link_state.block_name = @delegate_object[:block_name] =
1530
+ block_name_from_cli ? @cli_block_name : link_state.block_name
1531
+ end
1532
+
1533
+ # Outputs warnings based on the delegate object's configuration
1534
+ #
1535
+ # @param delegate_object [Hash] The delegate object containing configuration flags.
1536
+ # @param blocks_in_file [Hash] Hash of blocks present in the file.
1537
+ # @param menu_blocks [Hash] Hash of menu blocks.
1538
+ # @param link_state [LinkState] Current state of the link.
1539
+ def dump_delobj(blocks_in_file, menu_blocks, link_state)
1540
+ if @delegate_object[:dump_delegate_object]
1541
+ warn format_and_highlight_hash(@delegate_object, label: '@delegate_object')
1542
+ end
1543
+
1544
+ if @delegate_object[:dump_blocks_in_file]
1545
+ warn format_and_highlight_dependencies(compact_and_index_hash(blocks_in_file),
1546
+ label: 'blocks_in_file')
1547
+ end
1548
+
1549
+ if @delegate_object[:dump_menu_blocks]
1550
+ warn format_and_highlight_dependencies(compact_and_index_hash(menu_blocks),
1551
+ label: 'menu_blocks')
1552
+ end
1553
+
1554
+ return unless @delegate_object[:dump_inherited_lines]
1555
+
1556
+ warn format_and_highlight_lines(link_state.inherited_lines, label: 'inherited_lines')
1557
+ end
1558
+
1559
+ def dump_and_warn_block_state(block_state_block)
1560
+ if block_state_block.nil?
1561
+ Exceptions.warn_format("Block not found -- name: #{@delegate_object[:block_name]}",
1562
+ { abort: true })
1563
+ end
1564
+
1565
+ return unless @delegate_object[:dump_selected_block]
1566
+
1567
+ warn block_state_block.to_yaml.sub(/^(?:---\n)?/, "Block:\n")
1371
1568
  end
1372
1569
 
1373
1570
  # Presents a TTY prompt to select an option or exit, returns metadata including option and selected
@@ -1393,7 +1590,7 @@ module MarkdownExec
1393
1590
  rescue TTY::Reader::InputInterrupt
1394
1591
  exit 1
1395
1592
  rescue StandardError
1396
- error_handler('select_option_with_metadata')
1593
+ HashDelegator.error_handler('select_option_with_metadata')
1397
1594
  end
1398
1595
 
1399
1596
  def set_environment_variables_for_block(selected)
@@ -1407,22 +1604,8 @@ module MarkdownExec
1407
1604
  end
1408
1605
  end
1409
1606
 
1410
- def set_environment_variables_per_array(vars)
1411
- vars ||= []
1412
- vars.each { |key, value| ENV[key] = value.to_s }
1413
- end
1414
-
1415
- def set_file_permissions(file_path, chmod_value)
1416
- File.chmod(chmod_value, file_path)
1417
- end
1418
-
1419
- def set_script_block_name(selected)
1420
- @run_state.script_block_name = selected[:oname]
1421
- end
1422
-
1423
1607
  def should_add_back_option?
1424
1608
  @delegate_object[:menu_with_back] && @link_history.prior_state_exist?
1425
- # @delegate_object[:menu_with_back] && link_history_prior_state_exist?
1426
1609
  end
1427
1610
 
1428
1611
  # Initializes a new fenced code block (FCB) object based on the provided line and heading information.
@@ -1466,44 +1649,8 @@ module MarkdownExec
1466
1649
  # @param color_sym [Symbol] The symbol representing the color method.
1467
1650
  # @param default [String] Default color method to use if color_sym is not found in @delegate_object.
1468
1651
  # @return [String] The string with the applied color method.
1469
- def string_send_color(string, color_sym, default: 'plain')
1470
- color_method = @delegate_object.fetch(color_sym, default).to_sym
1471
- string.to_s.send(color_method)
1472
- end
1473
-
1474
- # Creates a TTY prompt with custom settings. Specifically, it disables the default 'cross' symbol and
1475
- # defines a lambda function to handle interrupts.
1476
- # @return [TTY::Prompt] A new TTY::Prompt instance with specified configurations.
1477
- def tty_prompt_without_disabled_symbol
1478
- TTY::Prompt.new(
1479
- interrupt: lambda {
1480
- puts
1481
- raise TTY::Reader::InputInterrupt
1482
- },
1483
- symbols: { cross: ' ' }
1484
- )
1485
- end
1486
-
1487
- # Updates the hierarchy of document headings based on the given line.
1488
- # Utilizes regular expressions to identify heading levels.
1489
- # @param line [String] The line of text to check for headings.
1490
- # @param headings [Array<String>] Current headings hierarchy.
1491
- # @return [Array<String>] Updated headings hierarchy.
1492
- def update_document_headings(line, headings)
1493
- heading3_match = Regexp.new(@delegate_object[:heading3_match])
1494
- heading2_match = Regexp.new(@delegate_object[:heading2_match])
1495
- heading1_match = Regexp.new(@delegate_object[:heading1_match])
1496
-
1497
- case line
1498
- when heading3_match
1499
- [headings[0], headings[1], $~[:name]]
1500
- when heading2_match
1501
- [headings[0], $~[:name]]
1502
- when heading1_match
1503
- [$~[:name]]
1504
- else
1505
- headings
1506
- end
1652
+ def string_send_color(string, color_sym)
1653
+ HashDelegator.apply_color_from_hash(string, @delegate_object, color_sym)
1507
1654
  end
1508
1655
 
1509
1656
  ##
@@ -1528,16 +1675,12 @@ module MarkdownExec
1528
1675
  def update_line_and_block_state(nested_line, state, selected_messages,
1529
1676
  &block)
1530
1677
  line = nested_line.to_s
1531
- if @delegate_object[:menu_blocks_with_headings]
1532
- state[:headings] = update_document_headings(line, state[:headings])
1533
- end
1534
-
1535
1678
  if line.match(@delegate_object[:fenced_start_and_end_regex])
1536
1679
  if state[:in_fenced_block]
1537
1680
  ## end of code block
1538
1681
  #
1539
- update_menu_attrib_yield_selected(state[:fcb], selected_messages,
1540
- &block)
1682
+ HashDelegator.update_menu_attrib_yield_selected(state[:fcb], selected_messages, @delegate_object,
1683
+ &block)
1541
1684
  state[:in_fenced_block] = false
1542
1685
  else
1543
1686
  ## start of code block
@@ -1558,29 +1701,14 @@ module MarkdownExec
1558
1701
  elsif nested_line[:depth].zero? || @delegate_object[:menu_include_imported_notes]
1559
1702
  # add line if it is depth 0 or option allows it
1560
1703
  #
1561
- yield_line_if_selected(line, selected_messages, &block)
1704
+ HashDelegator.yield_line_if_selected(line, selected_messages, &block)
1562
1705
 
1563
1706
  else
1564
- # 'rejected'
1707
+ # &bsp 'line is not recognized for block state'
1565
1708
 
1566
1709
  end
1567
1710
  end
1568
1711
 
1569
- # Updates the attributes of the given fcb object and conditionally yields to a block.
1570
- # It initializes fcb names and sets the default block title from fcb's body.
1571
- # If the fcb has a body and meets certain conditions, it yields to the given block.
1572
- #
1573
- # @param fcb [Object] The fcb object whose attributes are to be updated.
1574
- # @param selected_messages [Array<Symbol>] A list of message types to determine if yielding is applicable.
1575
- # @param block [Block] An optional block to yield to if conditions are met.
1576
- def update_menu_attrib_yield_selected(fcb, selected_messages, &block)
1577
- initialize_fcb_names(fcb)
1578
- return unless fcb.body
1579
-
1580
- default_block_title_from_body(fcb)
1581
- yield_to_block_if_applicable(fcb, selected_messages, &block)
1582
- end
1583
-
1584
1712
  # Processes YAML data from the selected menu item, updating delegate objects and optionally printing formatted output.
1585
1713
  # @param selected [Hash] Selected item from the menu containing a YAML body.
1586
1714
  # @param tgt2 [Hash, nil] An optional target hash to update with YAML data.
@@ -1610,10 +1738,10 @@ module MarkdownExec
1610
1738
 
1611
1739
  def wait_for_user_selected_block(all_blocks, menu_blocks, default)
1612
1740
  block_state = wait_for_user_selection(all_blocks, menu_blocks, default)
1613
- handle_block_state(block_state)
1741
+ handle_back_or_continue(block_state)
1614
1742
  block_state
1615
1743
  rescue StandardError
1616
- error_handler('wait_for_user_selected_block')
1744
+ HashDelegator.error_handler('wait_for_user_selected_block')
1617
1745
  end
1618
1746
 
1619
1747
  def wait_for_user_selection(_all_blocks, menu_blocks, default)
@@ -1664,32 +1792,13 @@ module MarkdownExec
1664
1792
  "# time: #{time_now}\n" \
1665
1793
  "#{required_lines.flatten.join("\n")}\n"
1666
1794
 
1667
- create_file_and_write_string_with_permissions(
1795
+ HashDelegator.create_file_and_write_string_with_permissions(
1668
1796
  @run_state.saved_filespec,
1669
1797
  content,
1670
1798
  @delegate_object[:saved_script_chmod]
1671
1799
  )
1672
1800
  rescue StandardError
1673
- error_handler('write_command_file')
1674
- end
1675
-
1676
- def save_executed_script_if_specified(lines)
1677
- write_command_file(lines) if @delegate_object[:save_executed_script]
1678
- end
1679
-
1680
- def write_execution_output_to_file
1681
- FileUtils.mkdir_p File.dirname(@delegate_object[:logged_stdout_filespec])
1682
-
1683
- File.write(
1684
- @delegate_object[:logged_stdout_filespec],
1685
- ["-STDOUT-\n",
1686
- format_execution_streams(ExecutionStreams::StdOut),
1687
- "-STDERR-\n",
1688
- format_execution_streams(ExecutionStreams::StdErr),
1689
- "-STDIN-\n",
1690
- format_execution_streams(ExecutionStreams::StdIn),
1691
- "\n"].join
1692
- )
1801
+ HashDelegator.error_handler('write_command_file')
1693
1802
  end
1694
1803
 
1695
1804
  # Writes required code blocks to a temporary file and sets an environment variable with its path.
@@ -1707,40 +1816,10 @@ module MarkdownExec
1707
1816
  []
1708
1817
  end
1709
1818
 
1710
- code_blocks = (read_required_blocks_from_temp_file(import_filename) +
1819
+ code_blocks = (HashDelegator.read_required_blocks_from_temp_file(import_filename) +
1711
1820
  c1).join("\n")
1712
1821
 
1713
- write_code_to_file(code_blocks, temp_file_path)
1714
- end
1715
-
1716
- # Writes the provided code blocks to a file.
1717
- # @param code_blocks [String] Code blocks to write into the file.
1718
- def write_code_to_file(content, path)
1719
- File.write(path, content)
1720
- end
1721
-
1722
- # Yields a line as a new block if the selected message type includes :line.
1723
- # @param [String] line The line to be processed.
1724
- # @param [Array<Symbol>] selected_messages A list of message types to check.
1725
- # @param [Proc] block The block to be called with the line data.
1726
- def yield_line_if_selected(line, selected_messages, &block)
1727
- return unless block && selected_messages.include?(:line)
1728
-
1729
- block.call(:line, FCB.new(body: [line]))
1730
- end
1731
-
1732
- # Yields to the provided block with specified parameters if certain conditions are met.
1733
- # The method checks if a block is given, if the selected_messages include :blocks,
1734
- # and if the fcb_select? method from MarkdownExec::Filter returns true for the given fcb.
1735
- #
1736
- # @param fcb [Object] The object to be evaluated and potentially passed to the block.
1737
- # @param selected_messages [Array<Symbol>] A collection of message types, one of which must be :blocks.
1738
- # @param block [Block] A block to be called if conditions are met.
1739
- def yield_to_block_if_applicable(fcb, selected_messages, &block)
1740
- if block_given? && selected_messages.include?(:blocks) &&
1741
- MarkdownExec::Filter.fcb_select?(@delegate_object, fcb)
1742
- block.call :blocks, fcb
1743
- end
1822
+ HashDelegator.write_code_to_file(code_blocks, temp_file_path)
1744
1823
  end
1745
1824
  end
1746
1825
  end
@@ -1810,30 +1889,30 @@ if $PROGRAM_NAME == __FILE__
1810
1889
  body = "Line 1\nLine 2"
1811
1890
  indent = ' ' # Two spaces
1812
1891
  expected_result = " Line 1\n Line 2"
1813
- assert_equal expected_result, @hd.indent_all_lines(body, indent)
1892
+ assert_equal expected_result, HashDelegator.indent_all_lines(body, indent)
1814
1893
  end
1815
1894
 
1816
1895
  def test_indent_all_lines_without_indent
1817
1896
  body = "Line 1\nLine 2"
1818
1897
  indent = nil
1819
1898
 
1820
- assert_equal body, @hd.indent_all_lines(body, indent)
1899
+ assert_equal body, HashDelegator.indent_all_lines(body, indent)
1821
1900
  end
1822
1901
 
1823
1902
  def test_indent_all_lines_with_empty_indent
1824
1903
  body = "Line 1\nLine 2"
1825
1904
  indent = ''
1826
1905
 
1827
- assert_equal body, @hd.indent_all_lines(body, indent)
1906
+ assert_equal body, HashDelegator.indent_all_lines(body, indent)
1828
1907
  end
1829
1908
 
1830
1909
  def test_safeval_successful_evaluation
1831
- assert_equal 4, @hd.safeval('2 + 2')
1910
+ assert_equal 4, HashDelegator.safeval('2 + 2')
1832
1911
  end
1833
1912
 
1834
1913
  def test_safeval_rescue_from_error
1835
- @hd.stubs(:error_handler).with('safeval')
1836
- assert_nil @hd.safeval('invalid code')
1914
+ HashDelegator.stubs(:error_handler).with('safeval')
1915
+ assert_nil HashDelegator.safeval('invalid code')
1837
1916
  end
1838
1917
 
1839
1918
  def test_set_fcb_title
@@ -1861,7 +1940,7 @@ if $PROGRAM_NAME == __FILE__
1861
1940
  input_output_data.each do |data|
1862
1941
  input = data[:input]
1863
1942
  output = data[:output]
1864
- @hd.default_block_title_from_body(input)
1943
+ HashDelegator.default_block_title_from_body(input)
1865
1944
  assert_equal output, input.title
1866
1945
  end
1867
1946
  end
@@ -1876,7 +1955,7 @@ if $PROGRAM_NAME == __FILE__
1876
1955
  menu_divider_color: :color
1877
1956
  })
1878
1957
  @hd.stubs(:string_send_color).returns('Formatted Divider')
1879
- @hd.stubs(:safeval).returns('Safe Value')
1958
+ HashDelegator.stubs(:safeval).returns('Safe Value')
1880
1959
  end
1881
1960
 
1882
1961
  def test_append_divider_initial
@@ -1911,19 +1990,19 @@ if $PROGRAM_NAME == __FILE__
1911
1990
 
1912
1991
  def test_block_find_with_match
1913
1992
  blocks = [{ key: 'value1' }, { key: 'value2' }]
1914
- result = @hd.block_find(blocks, :key, 'value1')
1993
+ result = HashDelegator.block_find(blocks, :key, 'value1')
1915
1994
  assert_equal({ key: 'value1' }, result)
1916
1995
  end
1917
1996
 
1918
1997
  def test_block_find_without_match
1919
1998
  blocks = [{ key: 'value1' }, { key: 'value2' }]
1920
- result = @hd.block_find(blocks, :key, 'value3')
1999
+ result = HashDelegator.block_find(blocks, :key, 'value3')
1921
2000
  assert_nil result
1922
2001
  end
1923
2002
 
1924
2003
  def test_block_find_with_default
1925
2004
  blocks = [{ key: 'value1' }, { key: 'value2' }]
1926
- result = @hd.block_find(blocks, :key, 'value3', 'default')
2005
+ result = HashDelegator.block_find(blocks, :key, 'value3', 'default')
1927
2006
  assert_equal 'default', result
1928
2007
  end
1929
2008
  end
@@ -1935,7 +2014,7 @@ if $PROGRAM_NAME == __FILE__
1935
2014
  @hd.stubs(:get_block_summary).returns(FCB.new)
1936
2015
  @hd.stubs(:create_and_add_chrome_blocks)
1937
2016
  @hd.instance_variable_set(:@delegate_object, {})
1938
- @hd.stubs(:error_handler)
2017
+ HashDelegator.stubs(:error_handler)
1939
2018
  end
1940
2019
 
1941
2020
  def test_blocks_from_nested_files
@@ -1961,7 +2040,7 @@ if $PROGRAM_NAME == __FILE__
1961
2040
  @hd.instance_variable_set(:@delegate_object, {})
1962
2041
  @mdoc = mock('YourMDocClass')
1963
2042
  @selected = { shell: BlockType::VARS, body: ['key: value'] }
1964
- @hd.stubs(:read_required_blocks_from_temp_file).returns([])
2043
+ HashDelegator.stubs(:read_required_blocks_from_temp_file).returns([])
1965
2044
  @hd.stubs(:string_send_color)
1966
2045
  @hd.stubs(:print)
1967
2046
  end
@@ -1979,7 +2058,7 @@ if $PROGRAM_NAME == __FILE__
1979
2058
  def setup
1980
2059
  @hd = HashDelegator.new
1981
2060
  @hd.instance_variable_set(:@delegate_object, {})
1982
- @hd.stubs(:error_handler)
2061
+ HashDelegator.stubs(:error_handler)
1983
2062
  @hd.stubs(:wait_for_user_selected_block)
1984
2063
  end
1985
2064
 
@@ -1988,9 +2067,9 @@ if $PROGRAM_NAME == __FILE__
1988
2067
  @hd.instance_variable_set(:@delegate_object,
1989
2068
  { block_name: 'block1' })
1990
2069
 
1991
- result = @hd.command_or_user_selected_block(all_blocks, [], nil)
2070
+ result = @hd.load_cli_or_user_selected_block(all_blocks, [], nil)
1992
2071
 
1993
- assert_equal all_blocks.first, result.block
2072
+ assert_equal all_blocks.first.merge(block_name_from_ui: false), result.block
1994
2073
  assert_nil result.state
1995
2074
  end
1996
2075
 
@@ -1999,9 +2078,9 @@ if $PROGRAM_NAME == __FILE__
1999
2078
  :some_state)
2000
2079
  @hd.stubs(:wait_for_user_selected_block).returns(block_state)
2001
2080
 
2002
- result = @hd.command_or_user_selected_block([], [], nil)
2081
+ result = @hd.load_cli_or_user_selected_block([], [], nil)
2003
2082
 
2004
- assert_equal block_state.block, result.block
2083
+ assert_equal block_state.block.merge(block_name_from_ui: true), result.block
2005
2084
  assert_equal :some_state, result.state
2006
2085
  end
2007
2086
  end
@@ -2038,7 +2117,7 @@ if $PROGRAM_NAME == __FILE__
2038
2117
  class TestHashDelegatorCreateAndWriteFile < Minitest::Test
2039
2118
  def setup
2040
2119
  @hd = HashDelegator.new
2041
- @hd.stubs(:error_handler)
2120
+ HashDelegator.stubs(:error_handler)
2042
2121
  FileUtils.stubs(:mkdir_p)
2043
2122
  File.stubs(:write)
2044
2123
  File.stubs(:chmod)
@@ -2053,8 +2132,8 @@ if $PROGRAM_NAME == __FILE__
2053
2132
  File.expects(:write).with(file_path, content).once
2054
2133
  File.expects(:chmod).with(chmod_value, file_path).once
2055
2134
 
2056
- @hd.create_file_and_write_string_with_permissions(file_path, content,
2057
- chmod_value)
2135
+ HashDelegator.create_file_and_write_string_with_permissions(file_path, content,
2136
+ chmod_value)
2058
2137
 
2059
2138
  assert true # Placeholder for actual test assertions
2060
2139
  end
@@ -2068,8 +2147,8 @@ if $PROGRAM_NAME == __FILE__
2068
2147
  File.expects(:write).with(file_path, content).once
2069
2148
  File.expects(:chmod).never
2070
2149
 
2071
- @hd.create_file_and_write_string_with_permissions(file_path, content,
2072
- chmod_value)
2150
+ HashDelegator.create_file_and_write_string_with_permissions(file_path, content,
2151
+ chmod_value)
2073
2152
 
2074
2153
  assert true # Placeholder for actual test assertions
2075
2154
  end
@@ -2200,11 +2279,8 @@ if $PROGRAM_NAME == __FILE__
2200
2279
  end
2201
2280
 
2202
2281
  def test_format_execution_streams_with_valid_key
2203
- @hd.instance_variable_get(:@run_state).stubs(:files).returns({ stdout: %w[
2204
- output1 output2
2205
- ] })
2206
-
2207
- result = @hd.format_execution_streams(:stdout)
2282
+ result = HashDelegator.format_execution_streams(:stdout,
2283
+ { stdout: %w[output1 output2] })
2208
2284
 
2209
2285
  assert_equal 'output1output2', result
2210
2286
  end
@@ -2212,7 +2288,7 @@ if $PROGRAM_NAME == __FILE__
2212
2288
  def test_format_execution_streams_with_empty_key
2213
2289
  @hd.instance_variable_get(:@run_state).stubs(:files).returns({})
2214
2290
 
2215
- result = @hd.format_execution_streams(:stderr)
2291
+ result = HashDelegator.format_execution_streams(:stderr)
2216
2292
 
2217
2293
  assert_equal '', result
2218
2294
  end
@@ -2220,7 +2296,7 @@ if $PROGRAM_NAME == __FILE__
2220
2296
  def test_format_execution_streams_with_nil_files
2221
2297
  @hd.instance_variable_get(:@run_state).stubs(:files).returns(nil)
2222
2298
 
2223
- result = @hd.format_execution_streams(:stdin)
2299
+ result = HashDelegator.format_execution_streams(:stdin)
2224
2300
 
2225
2301
  assert_equal '', result
2226
2302
  end
@@ -2251,33 +2327,33 @@ if $PROGRAM_NAME == __FILE__
2251
2327
  @mock_block_state = mock('block_state')
2252
2328
  end
2253
2329
 
2254
- def test_handle_block_state_with_back
2330
+ def test_handle_back_or_continue_with_back
2255
2331
  @mock_block_state.stubs(:state).returns(MenuState::BACK)
2256
2332
  @mock_block_state.stubs(:block).returns({ oname: 'sample_block' })
2257
2333
 
2258
- @hd.handle_block_state(@mock_block_state)
2334
+ @hd.handle_back_or_continue(@mock_block_state)
2259
2335
 
2260
2336
  assert_equal 'sample_block',
2261
2337
  @hd.instance_variable_get(:@delegate_object)[:block_name]
2262
2338
  assert @hd.instance_variable_get(:@menu_user_clicked_back_link)
2263
2339
  end
2264
2340
 
2265
- def test_handle_block_state_with_continue
2341
+ def test_handle_back_or_continue_with_continue
2266
2342
  @mock_block_state.stubs(:state).returns(MenuState::CONTINUE)
2267
2343
  @mock_block_state.stubs(:block).returns({ oname: 'another_block' })
2268
2344
 
2269
- @hd.handle_block_state(@mock_block_state)
2345
+ @hd.handle_back_or_continue(@mock_block_state)
2270
2346
 
2271
2347
  assert_equal 'another_block',
2272
2348
  @hd.instance_variable_get(:@delegate_object)[:block_name]
2273
2349
  refute @hd.instance_variable_get(:@menu_user_clicked_back_link)
2274
2350
  end
2275
2351
 
2276
- def test_handle_block_state_with_other
2352
+ def test_handle_back_or_continue_with_other
2277
2353
  @mock_block_state.stubs(:state).returns(nil) # MenuState::OTHER
2278
2354
  @mock_block_state.stubs(:block).returns({ oname: 'other_block' })
2279
2355
 
2280
- @hd.handle_block_state(@mock_block_state)
2356
+ @hd.handle_back_or_continue(@mock_block_state)
2281
2357
 
2282
2358
  assert_nil @hd.instance_variable_get(:@delegate_object)[:block_name]
2283
2359
  assert_nil @hd.instance_variable_get(:@menu_user_clicked_back_link)
@@ -2291,7 +2367,7 @@ if $PROGRAM_NAME == __FILE__
2291
2367
  @selected_item = mock('FCB')
2292
2368
  end
2293
2369
 
2294
- def test_compile_execute_bash_and_special_blocks_and_trigger_reuse_without_user_approval
2370
+ def test_compile_execute_and_trigger_reuse_without_user_approval
2295
2371
  # Mock the delegate object configuration
2296
2372
  @hd.instance_variable_set(:@delegate_object,
2297
2373
  { output_script: false,
@@ -2301,7 +2377,7 @@ if $PROGRAM_NAME == __FILE__
2301
2377
  # Expectations and assertions go here
2302
2378
  end
2303
2379
 
2304
- def test_compile_execute_bash_and_special_blocks_and_trigger_reuse_with_user_approval
2380
+ def test_compile_execute_and_trigger_reuse_with_user_approval
2305
2381
  # Mock the delegate object configuration
2306
2382
  @hd.instance_variable_set(:@delegate_object,
2307
2383
  { output_script: false,
@@ -2311,7 +2387,7 @@ if $PROGRAM_NAME == __FILE__
2311
2387
  # Expectations and assertions go here
2312
2388
  end
2313
2389
 
2314
- def test_compile_execute_bash_and_special_blocks_and_trigger_reuse_with_output_script
2390
+ def test_compile_execute_and_trigger_reuse_with_output_script
2315
2391
  # Mock the delegate object configuration
2316
2392
  @hd.instance_variable_set(:@delegate_object,
2317
2393
  { output_script: true,
@@ -2422,7 +2498,7 @@ if $PROGRAM_NAME == __FILE__
2422
2498
  menu_option_back_name: "'Back'",
2423
2499
  menu_chrome_format: '-- %s --'
2424
2500
  })
2425
- @hd.stubs(:safeval).with("'Back'").returns('Back')
2501
+ HashDelegator.stubs(:safeval).with("'Back'").returns('Back')
2426
2502
  end
2427
2503
 
2428
2504
  def test_menu_chrome_formatted_option_with_format
@@ -2477,7 +2553,7 @@ if $PROGRAM_NAME == __FILE__
2477
2553
 
2478
2554
  def test_yield_line_if_selected_with_line
2479
2555
  block_called = false
2480
- @hd.yield_line_if_selected('Test line', [:line]) do |type, content|
2556
+ HashDelegator.yield_line_if_selected('Test line', [:line]) do |type, content|
2481
2557
  block_called = true
2482
2558
  assert_equal :line, type
2483
2559
  assert_equal 'Test line', content.body[0]
@@ -2487,70 +2563,47 @@ if $PROGRAM_NAME == __FILE__
2487
2563
 
2488
2564
  def test_yield_line_if_selected_without_line
2489
2565
  block_called = false
2490
- @hd.yield_line_if_selected('Test line', [:other]) do |_|
2566
+ HashDelegator.yield_line_if_selected('Test line', [:other]) do |_|
2491
2567
  block_called = true
2492
2568
  end
2493
2569
  refute block_called
2494
2570
  end
2495
2571
 
2496
2572
  def test_yield_line_if_selected_without_block
2497
- result = @hd.yield_line_if_selected('Test line', [:line])
2573
+ result = HashDelegator.yield_line_if_selected('Test line', [:line])
2498
2574
  assert_nil result
2499
2575
  end
2500
2576
  end
2501
2577
 
2502
- class TestHashDelegator < Minitest::Test
2503
- def setup
2504
- @hd = HashDelegator.new
2505
- @hd.instance_variable_set(:@delegate_object, {
2506
- heading1_match: '^# (?<name>.+)$',
2507
- heading2_match: '^## (?<name>.+)$',
2508
- heading3_match: '^### (?<name>.+)$'
2509
- })
2510
- end
2511
-
2512
- def test_update_document_headings
2513
- assert_equal(['Heading 1'],
2514
- @hd.update_document_headings('# Heading 1', []))
2515
- assert_equal(['Heading 1', 'Heading 2'],
2516
- @hd.update_document_headings('## Heading 2',
2517
- ['Heading 1']))
2518
- assert_equal(['Heading 1', 'Heading 2', 'Heading 3'],
2519
- @hd.update_document_headings('### Heading 3',
2520
- ['Heading 1', 'Heading 2']))
2521
- assert_equal([], @hd.update_document_headings('Regular text', []))
2522
- end
2523
- end
2524
-
2525
2578
  class TestHashDelegatorUpdateMenuAttribYieldSelectedWithBody < Minitest::Test
2526
2579
  def setup
2527
2580
  @hd = HashDelegator.new
2528
2581
  @fcb = mock('Fcb')
2529
2582
  @fcb.stubs(:body).returns(true)
2530
- @hd.stubs(:initialize_fcb_names)
2531
- @hd.stubs(:default_block_title_from_body)
2532
- @hd.stubs(:yield_to_block_if_applicable)
2583
+ HashDelegator.stubs(:initialize_fcb_names)
2584
+ HashDelegator.stubs(:default_block_title_from_body)
2585
+ Filter.stubs(:yield_to_block_if_applicable)
2533
2586
  end
2534
2587
 
2535
2588
  def test_update_menu_attrib_yield_selected_with_body
2536
- @hd.expects(:initialize_fcb_names).with(@fcb)
2537
- @hd.expects(:default_block_title_from_body).with(@fcb)
2538
- @hd.expects(:yield_to_block_if_applicable).with(@fcb, [:some_message])
2589
+ HashDelegator.expects(:initialize_fcb_names).with(@fcb)
2590
+ HashDelegator.expects(:default_block_title_from_body).with(@fcb)
2591
+ Filter.expects(:yield_to_block_if_applicable).with(@fcb, [:some_message], {})
2539
2592
 
2540
- @hd.update_menu_attrib_yield_selected(@fcb, [:some_message])
2593
+ HashDelegator.update_menu_attrib_yield_selected(@fcb, [:some_message])
2541
2594
  end
2542
2595
 
2543
2596
  def test_update_menu_attrib_yield_selected_without_body
2544
2597
  @fcb.stubs(:body).returns(nil)
2545
- @hd.expects(:initialize_fcb_names).with(@fcb)
2546
- @hd.update_menu_attrib_yield_selected(@fcb, [:some_message])
2598
+ HashDelegator.expects(:initialize_fcb_names).with(@fcb)
2599
+ HashDelegator.update_menu_attrib_yield_selected(@fcb, [:some_message])
2547
2600
  end
2548
2601
  end
2549
2602
 
2550
2603
  class TestHashDelegatorWaitForUserSelectedBlock < Minitest::Test
2551
2604
  def setup
2552
2605
  @hd = HashDelegator.new
2553
- @hd.stubs(:error_handler)
2606
+ HashDelegator.stubs(:error_handler)
2554
2607
  end
2555
2608
 
2556
2609
  def test_wait_for_user_selected_block_with_back_state
@@ -2592,7 +2645,7 @@ if $PROGRAM_NAME == __FILE__
2592
2645
 
2593
2646
  def test_yield_to_block_if_applicable_with_correct_conditions
2594
2647
  block_called = false
2595
- @hd.yield_to_block_if_applicable(@fcb, [:blocks]) do |type, fcb|
2648
+ Filter.yield_to_block_if_applicable(@fcb, [:blocks]) do |type, fcb|
2596
2649
  block_called = true
2597
2650
  assert_equal :blocks, type
2598
2651
  assert_equal @fcb, fcb
@@ -2601,14 +2654,14 @@ if $PROGRAM_NAME == __FILE__
2601
2654
  end
2602
2655
 
2603
2656
  def test_yield_to_block_if_applicable_without_block
2604
- result = @hd.yield_to_block_if_applicable(@fcb, [:blocks])
2657
+ result = Filter.yield_to_block_if_applicable(@fcb, [:blocks])
2605
2658
  assert_nil result
2606
2659
  end
2607
2660
 
2608
2661
  def test_yield_to_block_if_applicable_with_incorrect_conditions
2609
2662
  block_called = false
2610
2663
  MarkdownExec::Filter.stubs(:fcb_select?).returns(false)
2611
- @hd.yield_to_block_if_applicable(@fcb, [:non_blocks]) do |_|
2664
+ Filter.yield_to_block_if_applicable(@fcb, [:non_blocks]) do |_|
2612
2665
  block_called = true
2613
2666
  end
2614
2667
  refute block_called