markdown_exec 1.8.5 → 1.8.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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