markdown_exec 1.8.6 → 1.8.8

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.
@@ -1,6 +1,8 @@
1
- # encoding=utf-8
1
+ #!/usr/bin/env bundle exec ruby
2
2
  # frozen_string_literal: true
3
3
 
4
+ # encoding=utf-8
5
+
4
6
  require 'English'
5
7
  require 'clipboard'
6
8
  require 'fileutils'
@@ -37,6 +39,273 @@ class String
37
39
  end
38
40
  end
39
41
 
42
+ module HashDelegatorSelf
43
+ # def add_back_option(menu_blocks)
44
+ # append_chrome_block(menu_blocks, MenuState::BACK)
45
+ # end
46
+
47
+ # Applies an ANSI color method to a string using a specified color key.
48
+ # The method retrieves the color method from the provided hash. If the color key
49
+ # is not present in the hash, it uses a default color method.
50
+ # @param string [String] The string to be colored.
51
+ # @param color_methods [Hash] A hash where keys are color names (String/Symbol) and values are color methods.
52
+ # @param color_key [String, Symbol] The key representing the desired color method in the color_methods hash.
53
+ # @param default_method [String] (optional) Default color method to use if color_key is not found in color_methods. Defaults to 'plain'.
54
+ # @return [String] The colored string.
55
+ def apply_color_from_hash(string, color_methods, color_key, default_method: 'plain')
56
+ color_method = color_methods.fetch(color_key, default_method).to_sym
57
+ string.to_s.send(color_method)
58
+ end
59
+
60
+ # # Enhanced `apply_color_from_hash` method to support dynamic color transformations
61
+ # # @param string [String] The string to be colored.
62
+ # # @param color_transformations [Hash] A hash mapping color names to lambdas that apply color transformations.
63
+ # # @param color_key [String, Symbol] The key representing the desired color transformation in the color_transformations hash.
64
+ # # @param default_transformation [Proc] Default color transformation to use if color_key is not found in color_transformations.
65
+ # # @return [String] The colored string.
66
+ # def apply_color_from_hash(string, color_transformations, color_key, default_transformation: ->(str) { str })
67
+ # transformation = color_transformations.fetch(color_key.to_sym, default_transformation)
68
+ # transformation.call(string)
69
+ # end
70
+ # color_transformations = {
71
+ # red: ->(str) { "\e[31m#{str}\e[0m" }, # ANSI color code for red
72
+ # green: ->(str) { "\e[32m#{str}\e[0m" }, # ANSI color code for green
73
+ # # Add more color transformations as needed
74
+ # }
75
+ # string = "Hello, World!"
76
+ # colored_string = apply_color_from_hash(string, color_transformations, :red)
77
+ # puts colored_string # This will print the string in red
78
+
79
+ # Searches for the first element in a collection where the specified key matches a given value.
80
+ # This method is particularly useful for finding a specific hash-like object within an enumerable collection.
81
+ # If no match is found, it returns a specified default value.
82
+ #
83
+ # @param blocks [Enumerable] The collection of hash-like objects to search.
84
+ # @param key [Object] The key to search for in each element of the collection.
85
+ # @param value [Object] The value to match against each element's corresponding key value.
86
+ # @param default [Object, nil] The default value to return if no match is found (optional).
87
+ # @return [Object, nil] The first matching element or the default value if no match is found.
88
+ def block_find(blocks, key, value, default = nil)
89
+ blocks.find { |item| item[key] == value } || default
90
+ end
91
+
92
+ def code_merge(*bodies)
93
+ merge_lists(*bodies)
94
+ end
95
+
96
+ def count_matches_in_lines(lines, regex)
97
+ lines.count { |line| line.to_s.match(regex) }
98
+ end
99
+
100
+ def create_directory_for_file(file_path)
101
+ FileUtils.mkdir_p(File.dirname(file_path))
102
+ end
103
+
104
+ # Creates a file at the specified path, writes the given content to it,
105
+ # and sets file permissions if required. Handles any errors encountered during the process.
106
+ #
107
+ # @param file_path [String] The path where the file will be created.
108
+ # @param content [String] The content to write into the file.
109
+ # @param chmod_value [Integer] The file permission value to set; skips if zero.
110
+ def create_file_and_write_string_with_permissions(file_path, content,
111
+ chmod_value)
112
+ create_directory_for_file(file_path)
113
+ File.write(file_path, content)
114
+ set_file_permissions(file_path, chmod_value) unless chmod_value.zero?
115
+ rescue StandardError
116
+ error_handler('create_file_and_write_string_with_permissions')
117
+ end
118
+
119
+ # def create_temp_file
120
+ # Dir::Tmpname.create(self.class.to_s) { |path| path }
121
+ # end
122
+
123
+ # Updates the title of an FCB object from its body content if the title is nil or empty.
124
+ def default_block_title_from_body(fcb)
125
+ return unless fcb.title.nil? || fcb.title.empty?
126
+
127
+ fcb.derive_title_from_body
128
+ end
129
+
130
+ # delete the current line if it is empty and the previous is also empty
131
+ def delete_consecutive_blank_lines!(blocks_menu)
132
+ blocks_menu.process_and_conditionally_delete! do |prev_item, current_item, _next_item|
133
+ prev_item&.fetch(:chrome, nil) && !prev_item&.fetch(:oname).present? &&
134
+ current_item&.fetch(:chrome, nil) && !current_item&.fetch(:oname).present?
135
+ end
136
+ end
137
+
138
+ # # Deletes a temporary file specified by an environment variable.
139
+ # # Checks if the file exists before attempting to delete it and clears the environment variable afterward.
140
+ # # Any errors encountered during deletion are handled gracefully.
141
+ # def delete_required_temp_file(temp_blocks_file_path)
142
+ # return if temp_blocks_file_path.nil? || temp_blocks_file_path.empty?
143
+
144
+ # HashDelegator.remove_file_without_standard_errors(temp_blocks_file_path)
145
+ # end
146
+
147
+ def error_handler(name = '', opts = {}, error: $!)
148
+ Exceptions.error_handler(
149
+ "HashDelegator.#{name} -- #{error}",
150
+ opts
151
+ )
152
+ end
153
+
154
+ # # DebugHelper.d ["HDmm method_name: #{method_name}", "#{first_n_caller_items 1}"]
155
+ # def first_n_caller_items(n)
156
+ # call_stack = caller
157
+ # base_path = File.realpath('.')
158
+
159
+ # # Modify the call stack to remove the base path and keep only the first n items
160
+ # call_stack.take(n + 1)[1..].map do |line|
161
+ # " . #{line.sub(/^#{Regexp.escape(base_path)}\//, '')}"
162
+ # end.join("\n")
163
+ # end
164
+
165
+ # Formats and returns the execution streams (like stdin, stdout, stderr) for a given key.
166
+ # It concatenates the array of strings found under the specified key in the run_state's files.
167
+ #
168
+ # @param key [Symbol] The key corresponding to the desired execution stream.
169
+ # @return [String] A concatenated string of the execution stream's contents.
170
+ def format_execution_streams(key, files = {})
171
+ (files || {}).fetch(key, []).join
172
+ end
173
+
174
+ # Indents all lines in a given string with a specified indentation string.
175
+ # @param body [String] A multi-line string to be indented.
176
+ # @param indent [String] The string used for indentation (default is an empty string).
177
+ # @return [String] A single string with each line indented as specified.
178
+ def indent_all_lines(body, indent = nil)
179
+ return body unless indent&.non_empty?
180
+
181
+ body.lines.map { |line| indent + line.chomp }.join("\n")
182
+ end
183
+
184
+ def initialize_fcb_names(fcb)
185
+ fcb.oname = fcb.dname = fcb.title || ''
186
+ end
187
+
188
+ def merge_lists(*args)
189
+ # Filters out nil values, flattens the arrays, and ensures an empty list is returned if no valid lists are provided
190
+ merged = args.compact.flatten
191
+ merged.empty? ? [] : merged
192
+ end
193
+
194
+ def next_link_state(block_name_from_cli, was_using_cli, block_state)
195
+ # &bsp 'next_link_state', block_name_from_cli, was_using_cli, block_state
196
+ # Set block_name based on block_name_from_cli
197
+ block_name = block_name_from_cli ? @cli_block_name : nil
198
+ # &bsp 'block_name:', block_name
199
+
200
+ # Determine the state of breaker based on was_using_cli and the block type
201
+ breaker = !block_name && !block_name_from_cli && was_using_cli && block_state.block[:shell] == BlockType::BASH
202
+ # &bsp 'breaker:', breaker
203
+
204
+ # Reset block_name_from_cli if the conditions are not met
205
+ block_name_from_cli ||= false
206
+ # &bsp 'block_name_from_cli:', block_name_from_cli
207
+
208
+ [block_name, block_name_from_cli, breaker]
209
+ end
210
+
211
+ def parse_yaml_data_from_body(body)
212
+ body.any? ? YAML.load(body.join("\n")) : {}
213
+ end
214
+
215
+ # Reads required code blocks from a temporary file specified by an environment variable.
216
+ # @return [Array<String>] Lines read from the temporary file, or an empty array if file is not found or path is empty.
217
+ def read_required_blocks_from_temp_file(temp_blocks_file_path)
218
+ return [] if temp_blocks_file_path.to_s.empty?
219
+
220
+ if File.exist?(temp_blocks_file_path)
221
+ File.readlines(
222
+ temp_blocks_file_path, chomp: true
223
+ )
224
+ else
225
+ []
226
+ end
227
+ end
228
+
229
+ def remove_file_without_standard_errors(path)
230
+ FileUtils.rm_f(path)
231
+ end
232
+
233
+ # Evaluates the given string as Ruby code and rescues any StandardErrors.
234
+ # If an error occurs, it calls the error_handler method with 'safeval'.
235
+ # @param str [String] The string to be evaluated.
236
+ # @return [Object] The result of evaluating the string.
237
+ def safeval(str)
238
+ eval(str)
239
+ rescue StandardError # catches NameError, StandardError
240
+ error_handler('safeval')
241
+ end
242
+
243
+ def set_file_permissions(file_path, chmod_value)
244
+ File.chmod(chmod_value, file_path)
245
+ end
246
+
247
+ # Creates a TTY prompt with custom settings. Specifically, it disables the default 'cross' symbol and
248
+ # defines a lambda function to handle interrupts.
249
+ # @return [TTY::Prompt] A new TTY::Prompt instance with specified configurations.
250
+ def tty_prompt_without_disabled_symbol
251
+ TTY::Prompt.new(
252
+ interrupt: lambda {
253
+ puts
254
+ raise TTY::Reader::InputInterrupt
255
+ },
256
+ symbols: { cross: ' ' }
257
+ )
258
+ end
259
+
260
+ # Updates the attributes of the given fcb object and conditionally yields to a block.
261
+ # It initializes fcb names and sets the default block title from fcb's body.
262
+ # If the fcb has a body and meets certain conditions, it yields to the given block.
263
+ #
264
+ # @param fcb [Object] The fcb object whose attributes are to be updated.
265
+ # @param selected_messages [Array<Symbol>] A list of message types to determine if yielding is applicable.
266
+ # @param block [Block] An optional block to yield to if conditions are met.
267
+ def update_menu_attrib_yield_selected(fcb, selected_messages, configuration = {}, &block)
268
+ initialize_fcb_names(fcb)
269
+ return unless fcb.body
270
+
271
+ default_block_title_from_body(fcb)
272
+ MarkdownExec::Filter.yield_to_block_if_applicable(fcb, selected_messages, configuration,
273
+ &block)
274
+ end
275
+
276
+ # Writes the provided code blocks to a file.
277
+ # @param code_blocks [String] Code blocks to write into the file.
278
+ def write_code_to_file(content, path)
279
+ File.write(path, content)
280
+ end
281
+
282
+ def write_execution_output_to_file(files, filespec)
283
+ FileUtils.mkdir_p File.dirname(filespec)
284
+
285
+ File.write(
286
+ filespec,
287
+ ["-STDOUT-\n",
288
+ format_execution_streams(ExecutionStreams::StdOut, files),
289
+ "-STDERR-\n",
290
+ format_execution_streams(ExecutionStreams::StdErr, files),
291
+ "-STDIN-\n",
292
+ format_execution_streams(ExecutionStreams::StdIn, files),
293
+ "\n"].join
294
+ )
295
+ end
296
+
297
+ # Yields a line as a new block if the selected message type includes :line.
298
+ # @param [String] line The line to be processed.
299
+ # @param [Array<Symbol>] selected_messages A list of message types to check.
300
+ # @param [Proc] block The block to be called with the line data.
301
+ def yield_line_if_selected(line, selected_messages, &block)
302
+ return unless block && selected_messages.include?(:line)
303
+
304
+ block.call(:line, MarkdownExec::FCB.new(body: [line]))
305
+ end
306
+ end
307
+ ### require_relative 'hash_delegator_self'
308
+
40
309
  # This module provides methods for compacting and converting data structures.
41
310
  module CompactionHelpers
42
311
  # Converts an array of key-value pairs into a hash, applying compaction to the values.
@@ -106,11 +375,12 @@ module MarkdownExec
106
375
  class HashDelegator
107
376
  attr_accessor :most_recent_loaded_filename, :pass_args, :run_state
108
377
 
378
+ extend HashDelegatorSelf
109
379
  include CompactionHelpers
110
380
 
111
381
  def initialize(delegate_object = {})
112
382
  @delegate_object = delegate_object
113
- @prompt = tty_prompt_without_disabled_symbol
383
+ @prompt = HashDelegator.tty_prompt_without_disabled_symbol
114
384
 
115
385
  @most_recent_loaded_filename = nil
116
386
  @pass_args = []
@@ -138,11 +408,20 @@ module MarkdownExec
138
408
  # along with initial and final dividers, based on the delegate object's configuration.
139
409
  #
140
410
  # @param menu_blocks [Array] The array of menu block elements to be modified.
141
- def add_menu_chrome_blocks!(menu_blocks)
411
+ def add_menu_chrome_blocks!(menu_blocks, link_state)
142
412
  return unless @delegate_object[:menu_link_format].present?
143
413
 
414
+ if @delegate_object[:menu_with_inherited_lines]
415
+ add_inherited_lines(menu_blocks,
416
+ link_state)
417
+ end
418
+
419
+ # back before exit
144
420
  add_back_option(menu_blocks) if should_add_back_option?
421
+
422
+ # exit after other options
145
423
  add_exit_option(menu_blocks) if @delegate_object[:menu_with_exit]
424
+
146
425
  add_dividers(menu_blocks)
147
426
  end
148
427
 
@@ -152,13 +431,17 @@ module MarkdownExec
152
431
  append_chrome_block(menu_blocks, MenuState::BACK)
153
432
  end
154
433
 
434
+ def add_dividers(menu_blocks)
435
+ append_divider(menu_blocks, :initial)
436
+ append_divider(menu_blocks, :final)
437
+ end
438
+
155
439
  def add_exit_option(menu_blocks)
156
440
  append_chrome_block(menu_blocks, MenuState::EXIT)
157
441
  end
158
442
 
159
- def add_dividers(menu_blocks)
160
- append_divider(menu_blocks, :initial)
161
- append_divider(menu_blocks, :final)
443
+ def add_inherited_lines(menu_blocks, link_state)
444
+ append_inherited_lines(menu_blocks, link_state)
162
445
  end
163
446
 
164
447
  public
@@ -179,7 +462,7 @@ module MarkdownExec
179
462
  end
180
463
 
181
464
  formatted_name = format(@delegate_object[:menu_link_format],
182
- safeval(option_name))
465
+ HashDelegator.safeval(option_name))
183
466
  chrome_block = FCB.new(
184
467
  chrome: true,
185
468
  dname: HashDelegator.new(@delegate_object).string_send_color(
@@ -195,6 +478,39 @@ module MarkdownExec
195
478
  end
196
479
  end
197
480
 
481
+ # Appends a formatted divider to the specified position in a menu block array.
482
+ # The method checks for the presence of formatting options before appending.
483
+ #
484
+ # @param menu_blocks [Array] The array of menu block elements.
485
+ # @param position [Symbol] The position to insert the divider (:initial or :final).
486
+ def append_inherited_lines(menu_blocks, link_state, position: top)
487
+ return unless link_state.inherited_lines.present?
488
+
489
+ insert_at_top = @delegate_object[:menu_inherited_lines_at_top]
490
+ chrome_blocks = link_state.inherited_lines.map do |line|
491
+ formatted = format(@delegate_object[:menu_inherited_lines_format],
492
+ { line: line })
493
+ FCB.new(
494
+ chrome: true,
495
+ disabled: '',
496
+ dname: HashDelegator.new(@delegate_object).string_send_color(
497
+ formatted, :menu_inherited_lines_color
498
+ ),
499
+ oname: formatted
500
+ )
501
+ end
502
+
503
+ if insert_at_top
504
+ # Prepend an array of elements to the beginning
505
+ menu_blocks.unshift(*chrome_blocks)
506
+ else
507
+ # Append an array of elements to the end
508
+ menu_blocks.concat(chrome_blocks)
509
+ end
510
+ rescue StandardError
511
+ HashDelegator.error_handler('append_inherited_lines')
512
+ end
513
+
198
514
  # Appends a formatted divider to the specified position in a menu block array.
199
515
  # The method checks for the presence of formatting options before appending.
200
516
  #
@@ -224,19 +540,6 @@ module MarkdownExec
224
540
 
225
541
  # private
226
542
 
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
543
  # Iterates through nested files to collect various types of blocks, including dividers, tasks, and others.
241
544
  # The method categorizes blocks based on their type and processes them accordingly.
242
545
  #
@@ -246,13 +549,30 @@ module MarkdownExec
246
549
  iter_blocks_from_nested_files do |btype, fcb|
247
550
  process_block_based_on_type(blocks, btype, fcb)
248
551
  end
552
+ # &bc 'blocks.count:', blocks.count
249
553
  blocks
250
554
  rescue StandardError
251
- error_handler('blocks_from_nested_files')
555
+ HashDelegator.error_handler('blocks_from_nested_files')
252
556
  end
253
557
 
254
558
  # private
255
559
 
560
+ def calc_logged_stdout_filename
561
+ return unless @delegate_object[:saved_stdout_folder]
562
+
563
+ @delegate_object[:logged_stdout_filename] =
564
+ SavedAsset.stdout_name(blockname: @delegate_object[:block_name],
565
+ filename: File.basename(@delegate_object[:filename],
566
+ '.*'),
567
+ prefix: @delegate_object[:logged_stdout_filename_prefix],
568
+ time: Time.now.utc)
569
+
570
+ @logged_stdout_filespec =
571
+ @delegate_object[:logged_stdout_filespec] =
572
+ File.join @delegate_object[:saved_stdout_folder],
573
+ @delegate_object[:logged_stdout_filename]
574
+ end
575
+
256
576
  def cfile
257
577
  @cfile ||= CachedNestedFileReader.new(
258
578
  import_pattern: @delegate_object.fetch(:import_pattern) #, "^ *@import +(?<name>.+?) *$")
@@ -273,15 +593,6 @@ module MarkdownExec
273
593
  true
274
594
  end
275
595
 
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
596
  # Collects required code lines based on the selected block and the delegate object's configuration.
286
597
  # If the block type is VARS, it also sets environment variables based on the block's content.
287
598
  #
@@ -292,7 +603,7 @@ module MarkdownExec
292
603
  set_environment_variables_for_block(selected) if selected[:shell] == BlockType::VARS
293
604
 
294
605
  required = mdoc.collect_recursively_required_code(
295
- @delegate_object[:block_name],
606
+ selected[:nickname] || selected[:oname],
296
607
  label_format_above: @delegate_object[:shell_code_label_format_above],
297
608
  label_format_below: @delegate_object[:shell_code_label_format_below],
298
609
  block_source: block_source
@@ -306,13 +617,14 @@ module MarkdownExec
306
617
  warn format_and_highlight_dependencies(dependencies,
307
618
  highlight: required[:unmet_dependencies])
308
619
  runtime_exception(:runtime_exception_error_level,
309
- 'unmet_dependencies, flag: runtime_exception_error_level', required[:unmet_dependencies])
620
+ 'unmet_dependencies, flag: runtime_exception_error_level',
621
+ required[:unmet_dependencies])
310
622
  elsif true
311
623
  warn format_and_highlight_dependencies(dependencies,
312
624
  highlight: [@delegate_object[:block_name]])
313
625
  end
314
626
 
315
- code_merge link_state&.inherited_lines, required[:code]
627
+ HashDelegator.code_merge(link_state&.inherited_lines, required[:code])
316
628
  end
317
629
 
318
630
  def command_execute(command, args: [])
@@ -320,26 +632,54 @@ module MarkdownExec
320
632
  @run_state.options = @delegate_object
321
633
  @run_state.started_at = Time.now.utc
322
634
 
323
- Open3.popen3(@delegate_object[:shell],
324
- '-c', command,
325
- @delegate_object[:filename],
326
- *args) do |stdin, stdout, stderr, exec_thr|
327
- handle_stream(stdout, ExecutionStreams::StdOut) do |line|
328
- yield nil, line, nil, exec_thr if block_given?
329
- end
330
- handle_stream(stderr, ExecutionStreams::StdErr) do |line|
331
- yield nil, nil, line, exec_thr if block_given?
332
- end
635
+ if @delegate_object[:execute_in_own_window] &&
636
+ @delegate_object[:execute_command_format].present? &&
637
+ @run_state.saved_filespec.present?
638
+ @run_state.in_own_window = true
639
+ system(
640
+ format(
641
+ @delegate_object[:execute_command_format],
642
+ {
643
+ batch_index: @run_state.batch_index,
644
+ batch_random: @run_state.batch_random,
645
+ block_name: @delegate_object[:block_name],
646
+ document_filename: File.basename(@delegate_object[:filename]),
647
+ document_filespec: @delegate_object[:filename],
648
+ home: Dir.pwd,
649
+ output_filename: File.basename(@delegate_object[:logged_stdout_filespec]),
650
+ output_filespec: @delegate_object[:logged_stdout_filespec],
651
+ script_filename: @run_state.saved_filespec,
652
+ script_filespec: File.join(Dir.pwd, @run_state.saved_filespec),
653
+ started_at: @run_state.started_at.strftime(
654
+ @delegate_object[:execute_command_title_time_format]
655
+ )
656
+ }
657
+ )
658
+ )
333
659
 
334
- in_thr = handle_stream($stdin, ExecutionStreams::StdIn) do |line|
335
- stdin.puts(line)
336
- yield line, nil, nil, exec_thr if block_given?
337
- end
660
+ else
661
+ @run_state.in_own_window = false
662
+ Open3.popen3(@delegate_object[:shell],
663
+ '-c', command,
664
+ @delegate_object[:filename],
665
+ *args) do |stdin, stdout, stderr, exec_thr|
666
+ handle_stream(stdout, ExecutionStreams::StdOut) do |line|
667
+ yield nil, line, nil, exec_thr if block_given?
668
+ end
669
+ handle_stream(stderr, ExecutionStreams::StdErr) do |line|
670
+ yield nil, nil, line, exec_thr if block_given?
671
+ end
672
+
673
+ in_thr = handle_stream($stdin, ExecutionStreams::StdIn) do |line|
674
+ stdin.puts(line)
675
+ yield line, nil, nil, exec_thr if block_given?
676
+ end
338
677
 
339
- wait_for_stream_processing
340
- exec_thr.join
341
- sleep 0.1
342
- in_thr.kill if in_thr&.alive?
678
+ wait_for_stream_processing
679
+ exec_thr.join
680
+ sleep 0.1
681
+ in_thr.kill if in_thr&.alive?
682
+ end
343
683
  end
344
684
 
345
685
  @run_state.completed_at = Time.now.utc
@@ -363,17 +703,17 @@ module MarkdownExec
363
703
  if @delegate_object[:block_name].present?
364
704
  block = all_blocks.find do |item|
365
705
  item[:oname] == @delegate_object[:block_name]
366
- end
706
+ end&.merge(block_name_from_ui: false)
367
707
  else
368
708
  block_state = wait_for_user_selected_block(all_blocks, menu_blocks,
369
709
  default)
370
- block = block_state.block
710
+ block = block_state.block&.merge(block_name_from_ui: true)
371
711
  state = block_state.state
372
712
  end
373
713
 
374
714
  SelectedBlockMenuState.new(block, state)
375
715
  rescue StandardError
376
- error_handler('load_cli_or_user_selected_block')
716
+ HashDelegator.error_handler('load_cli_or_user_selected_block')
377
717
  end
378
718
 
379
719
  # This method is responsible for handling the execution of generic blocks in a markdown document.
@@ -388,9 +728,13 @@ module MarkdownExec
388
728
  block_source: block_source)
389
729
  output_or_approval = @delegate_object[:output_script] || @delegate_object[:user_must_approve]
390
730
  display_required_code(required_lines) if output_or_approval
391
- allow_execution = @delegate_object[:user_must_approve] ? prompt_for_user_approval(required_lines) : true
731
+ allow_execution = if @delegate_object[:user_must_approve]
732
+ prompt_for_user_approval(required_lines, selected)
733
+ else
734
+ true
735
+ end
392
736
 
393
- execute_required_lines(required_lines) if allow_execution
737
+ execute_required_lines(required_lines, selected) if allow_execution
394
738
 
395
739
  link_state.block_name = nil
396
740
  LoadFileLinkState.new(LoadFile::Reuse, link_state)
@@ -411,18 +755,11 @@ module MarkdownExec
411
755
  # @return [Integer] The count of fenced code blocks in the file.
412
756
  def count_blocks_in_filename
413
757
  regex = Regexp.new(@delegate_object[:fenced_start_and_end_regex])
414
- lines = cfile.readlines(@delegate_object[:filename])
415
- count_matches_in_lines(lines, regex) / 2
416
- end
417
-
418
- # private
419
-
420
- def count_matches_in_lines(lines, regex)
421
- lines.count { |line| line.to_s.match(regex) }
758
+ lines = cfile.readlines(@delegate_object[:filename],
759
+ import_paths: @delegate_object[:import_paths]&.split(':'))
760
+ HashDelegator.count_matches_in_lines(lines, regex) / 2
422
761
  end
423
762
 
424
- # private
425
-
426
763
  ##
427
764
  # Creates and adds a formatted block to the blocks array based on the provided match and format options.
428
765
  # @param blocks [Array] The array of blocks to add the new block to.
@@ -449,13 +786,14 @@ module MarkdownExec
449
786
  # @param use_chrome [Boolean] Indicates if the chrome styling should be applied.
450
787
  def create_and_add_chrome_blocks(blocks, fcb)
451
788
  match_criteria = [
452
- { match: :menu_task_match, format: :menu_task_format,
453
- color: :menu_task_color },
454
- { match: :menu_divider_match, format: :menu_divider_format,
455
- color: :menu_divider_color },
456
- { match: :menu_note_match, format: :menu_note_format,
457
- color: :menu_note_color }
789
+ { color: :menu_heading1_color, format: :menu_heading1_format, match: :heading1_match },
790
+ { color: :menu_heading2_color, format: :menu_heading2_format, match: :heading2_match },
791
+ { color: :menu_heading3_color, format: :menu_heading3_format, match: :heading3_match },
792
+ { color: :menu_divider_color, format: :menu_divider_format, match: :menu_divider_match },
793
+ { color: :menu_note_color, format: :menu_note_format, match: :menu_note_match },
794
+ { color: :menu_task_color, format: :menu_task_format, match: :menu_task_match }
458
795
  ]
796
+ # rubocop:enable Style/UnlessElse
459
797
  match_criteria.each do |criteria|
460
798
  unless @delegate_object[criteria[:match]].present? &&
461
799
  (mbody = fcb.body[0].match @delegate_object[criteria[:match]])
@@ -468,31 +806,10 @@ module MarkdownExec
468
806
  end
469
807
  end
470
808
 
471
- # Creates a file at the specified path, writes the given content to it,
472
- # and sets file permissions if required. Handles any errors encountered during the process.
473
- #
474
- # @param file_path [String] The path where the file will be created.
475
- # @param content [String] The content to write into the file.
476
- # @param chmod_value [Integer] The file permission value to set; skips if zero.
477
- def create_file_and_write_string_with_permissions(file_path, content,
478
- chmod_value)
479
- create_directory_for_file(file_path)
480
- File.write(file_path, content)
481
- set_file_permissions(file_path, chmod_value) unless chmod_value.zero?
482
- rescue StandardError
483
- error_handler('create_file_and_write_string_with_permissions')
484
- end
485
-
486
- # private
487
-
488
- def create_directory_for_file(file_path)
489
- FileUtils.mkdir_p(File.dirname(file_path))
490
- end
491
-
492
809
  def create_divider(position)
493
810
  divider_key = position == :initial ? :menu_initial_divider : :menu_final_divider
494
811
  oname = format(@delegate_object[:menu_divider_format],
495
- safeval(@delegate_object[divider_key]))
812
+ HashDelegator.safeval(@delegate_object[divider_key]))
496
813
 
497
814
  FCB.new(
498
815
  chrome: true,
@@ -502,36 +819,27 @@ module MarkdownExec
502
819
  )
503
820
  end
504
821
 
505
- # private
506
-
507
- def create_temp_file
508
- Dir::Tmpname.create(self.class.to_s) { |path| path }
509
- end
510
-
511
- # Updates the title of an FCB object from its body content if the title is nil or empty.
512
- def default_block_title_from_body(fcb)
513
- return unless fcb.title.nil? || fcb.title.empty?
822
+ # Prompts user if named block is the same as the prior execution.
823
+ #
824
+ # @return [Boolean] Execute the named block.
825
+ def debounce_allows
826
+ return true unless @delegate_object[:debounce_execution]
514
827
 
515
- fcb.derive_title_from_body
516
- end
828
+ # filter block if selected in menu
829
+ return true if @run_state.block_name_from_cli
517
830
 
518
- # delete the current line if it is empty and the previous is also empty
519
- def delete_consecutive_blank_lines!(blocks_menu)
520
- blocks_menu.process_and_conditionally_delete! do |prev_item, current_item, _next_item|
521
- prev_item&.fetch(:chrome, nil) && !prev_item&.fetch(:oname).present? &&
522
- current_item&.fetch(:chrome, nil) && !current_item&.fetch(:oname).present?
831
+ # return false if @prior_execution_block == @delegate_object[:block_name]
832
+ if @prior_execution_block == @delegate_object[:block_name]
833
+ return @allowed_execution_block == @prior_execution_block || prompt_approve_repeat
523
834
  end
524
- end
525
835
 
526
- # Deletes a temporary file specified by an environment variable.
527
- # Checks if the file exists before attempting to delete it and clears the environment variable afterward.
528
- # Any errors encountered during deletion are handled gracefully.
529
- def delete_required_temp_file(temp_blocks_file_path)
530
- return if temp_blocks_file_path.nil? || temp_blocks_file_path.empty?
836
+ @prior_execution_block = @delegate_object[:block_name]
837
+ @allowed_execution_block = nil
838
+ true
839
+ end
531
840
 
532
- safely_remove_file(temp_blocks_file_path)
533
- rescue StandardError
534
- error_handler('delete_required_temp_file')
841
+ def debounce_reset
842
+ @prior_execution_block = nil
535
843
  end
536
844
 
537
845
  # Determines the state of a selected block in the menu based on the selected option.
@@ -570,14 +878,13 @@ module MarkdownExec
570
878
  @delegate_object[:menu_divider_format].present? && @delegate_object[divider_key].present?
571
879
  end
572
880
 
573
- def error_handler(name = '', opts = {})
574
- Exceptions.error_handler(
575
- "HashDelegator.#{name} -- #{$!}",
576
- opts
577
- )
578
- end
881
+ def do_save_execution_output
882
+ return unless @delegate_object[:save_execution_output]
883
+ return if @run_state.in_own_window
579
884
 
580
- # public
885
+ HashDelegator.write_execution_output_to_file(@run_state.files,
886
+ @delegate_object[:logged_stdout_filespec])
887
+ end
581
888
 
582
889
  # Executes a block of code that has been approved for execution.
583
890
  # It sets the script block name, writes command files if required, and handles the execution
@@ -585,9 +892,9 @@ module MarkdownExec
585
892
  #
586
893
  # @param required_lines [Array<String>] The lines of code to be executed.
587
894
  # @param selected [FCB] The selected functional code block object.
588
- def execute_required_lines(required_lines = [])
589
- # set_script_block_name(selected)
590
- save_executed_script_if_specified(required_lines)
895
+ def execute_required_lines(required_lines = [], selected = FCB.new)
896
+ write_command_file(required_lines, selected) if @delegate_object[:save_executed_script]
897
+ calc_logged_stdout_filename
591
898
  format_and_execute_command(required_lines)
592
899
  post_execution_process
593
900
  end
@@ -604,21 +911,26 @@ module MarkdownExec
604
911
  def execute_shell_type(selected, mdoc, link_state = LinkState.new,
605
912
  block_source:)
606
913
  if selected.fetch(:shell, '') == BlockType::LINK
914
+ debounce_reset
607
915
  push_link_history_and_trigger_load(selected.fetch(:body, ''), mdoc, selected,
608
916
  link_state)
609
917
 
610
918
  elsif @menu_user_clicked_back_link
919
+ debounce_reset
611
920
  pop_link_history_and_trigger_load
612
921
 
613
922
  elsif selected[:shell] == BlockType::OPTS
923
+ debounce_reset
614
924
  options_state = read_show_options_and_trigger_reuse(selected, link_state)
615
925
  @menu_base_options.merge!(options_state.options)
616
926
  @delegate_object.merge!(options_state.options)
617
927
  options_state.load_file_link_state
618
928
 
619
- else
929
+ elsif debounce_allows
620
930
  compile_execute_and_trigger_reuse(mdoc, selected, link_state,
621
931
  block_source: block_source)
932
+ else
933
+ LoadFileLinkState.new(LoadFile::Reuse, link_state)
622
934
  end
623
935
  end
624
936
 
@@ -636,17 +948,6 @@ module MarkdownExec
636
948
  string_send_color(data_string, color_sym)
637
949
  end
638
950
 
639
- # DebugHelper.d ["HDmm method_name: #{method_name}", "#{first_n_caller_items 1}"]
640
- def first_n_caller_items(n)
641
- call_stack = caller
642
- base_path = File.realpath('.')
643
-
644
- # Modify the call stack to remove the base path and keep only the first n items
645
- call_stack.take(n + 1)[1..].map do |line|
646
- " . #{line.sub(/^#{Regexp.escape(base_path)}\//, '')}"
647
- end.join("\n")
648
- end
649
-
650
951
  def format_and_execute_command(lines)
651
952
  formatted_command = lines.flatten.join("\n")
652
953
  @fout.fout fetch_color(data_sym: :script_execution_head,
@@ -672,16 +973,6 @@ module MarkdownExec
672
973
  string_send_color(formatted_string, color_sym)
673
974
  end
674
975
 
675
- # Formats and returns the execution streams (like stdin, stdout, stderr) for a given key.
676
- # It concatenates the array of strings found under the specified key in the run_state's files.
677
- #
678
- # @param key [Symbol] The key corresponding to the desired execution stream.
679
- # @return [String] A concatenated string of the execution stream's contents.
680
- def format_execution_streams(key)
681
- files = @run_state.files || {}
682
- files.fetch(key, []).join
683
- end
684
-
685
976
  # Processes a block to generate its summary, modifying its attributes based on various matching criteria.
686
977
  # It handles special formatting for bash blocks, extracting and setting properties like call, stdin, stdout, and dname.
687
978
  #
@@ -701,17 +992,36 @@ module MarkdownExec
701
992
  @delegate_object[:block_stdout_scan])
702
993
 
703
994
  shell_color_option = SHELL_COLOR_OPTIONS[fcb[:shell]]
704
- fcb.title = fcb.oname = bm && bm[1] ? bm[:title] : titlexcall
705
- fcb.dname = apply_shell_color_option(fcb.oname, shell_color_option)
706
995
 
996
+ if @delegate_object[:block_name_nick_match].present? && fcb.oname =~ Regexp.new(@delegate_object[:block_name_nick_match])
997
+ fcb.nickname = $~[0]
998
+ fcb.title = fcb.oname = format_multiline_body_as_title(fcb.body)
999
+ else
1000
+ fcb.title = fcb.oname = bm && bm[1] ? bm[:title] : titlexcall
1001
+ end
1002
+
1003
+ fcb.dname = HashDelegator.indent_all_lines(
1004
+ apply_shell_color_option(fcb.oname, shell_color_option),
1005
+ fcb.fetch(:indent, nil)
1006
+ )
707
1007
  fcb
708
1008
  end
709
1009
 
1010
+ # Formats multiline body content as a title string.
1011
+ # indents all but first line with two spaces so it displays correctly in menu
1012
+ # @param body_lines [Array<String>] The lines of body content.
1013
+ # @return [String] Formatted title.
1014
+ def format_multiline_body_as_title(body_lines)
1015
+ body_lines.map.with_index do |line, index|
1016
+ index.zero? ? line : " #{line}"
1017
+ end.join("\n") + "\n"
1018
+ end
1019
+
710
1020
  # Updates the delegate object's state based on the provided block state.
711
1021
  # It sets the block name and determines if the user clicked the back link in the menu.
712
1022
  #
713
1023
  # @param block_state [Object] An object representing the state of a block in the menu.
714
- def handle_block_state(block_state)
1024
+ def handle_back_or_continue(block_state)
715
1025
  return if block_state.nil?
716
1026
  unless [MenuState::BACK,
717
1027
  MenuState::CONTINUE].include?(block_state.state)
@@ -744,16 +1054,6 @@ module MarkdownExec
744
1054
  end
745
1055
  end
746
1056
 
747
- # Indents all lines in a given string with a specified indentation string.
748
- # @param body [String] A multi-line string to be indented.
749
- # @param indent [String] The string used for indentation (default is an empty string).
750
- # @return [String] A single string with each line indented as specified.
751
- def indent_all_lines(body, indent = nil)
752
- return body unless indent&.non_empty?
753
-
754
- body.lines.map { |line| indent + line.chomp }.join("\n")
755
- end
756
-
757
1057
  # Initializes variables for regex and other states
758
1058
  def initial_state
759
1059
  {
@@ -769,28 +1069,6 @@ module MarkdownExec
769
1069
  }
770
1070
  end
771
1071
 
772
- def initialize_and_save_execution_output
773
- return unless @delegate_object[:save_execution_output]
774
-
775
- @delegate_object[:logged_stdout_filename] =
776
- SavedAsset.stdout_name(blockname: @delegate_object[:block_name],
777
- filename: File.basename(@delegate_object[:filename],
778
- '.*'),
779
- prefix: @delegate_object[:logged_stdout_filename_prefix],
780
- time: Time.now.utc)
781
-
782
- @logged_stdout_filespec =
783
- @delegate_object[:logged_stdout_filespec] =
784
- File.join @delegate_object[:saved_stdout_folder],
785
- @delegate_object[:logged_stdout_filename]
786
- @logged_stdout_filespec = @delegate_object[:logged_stdout_filespec]
787
- write_execution_output_to_file
788
- end
789
-
790
- def initialize_fcb_names(fcb)
791
- fcb.oname = fcb.dname = fcb.title || ''
792
- end
793
-
794
1072
  # Iterates through blocks in a file, applying the provided block to each line.
795
1073
  # The iteration only occurs if the file exists.
796
1074
  # @yield [Symbol] :filter Yields to obtain selected messages for processing.
@@ -799,8 +1077,8 @@ module MarkdownExec
799
1077
 
800
1078
  state = initial_state
801
1079
  selected_messages = yield :filter
802
-
803
- cfile.readlines(@delegate_object[:filename]).each do |nested_line|
1080
+ cfile.readlines(@delegate_object[:filename],
1081
+ import_paths: @delegate_object[:import_paths]&.split(':')).each do |nested_line|
804
1082
  if nested_line
805
1083
  update_line_and_block_state(nested_line, state, selected_messages,
806
1084
  &block)
@@ -808,6 +1086,65 @@ module MarkdownExec
808
1086
  end
809
1087
  end
810
1088
 
1089
+ def link_block_data_eval(link_state, code_lines, selected, link_block_data)
1090
+ all_code = HashDelegator.code_merge(link_state&.inherited_lines, code_lines)
1091
+
1092
+ if link_block_data.fetch(LinkDataKeys::Exec, false)
1093
+ @run_state.files = Hash.new([])
1094
+ output_lines = []
1095
+
1096
+ Open3.popen3(
1097
+ @delegate_object[:shell],
1098
+ '-c', all_code.join("\n")
1099
+ ) do |stdin, stdout, stderr, _exec_thr|
1100
+ handle_stream(stdout, ExecutionStreams::StdOut) do |line|
1101
+ output_lines.push(line)
1102
+ end
1103
+ handle_stream(stderr, ExecutionStreams::StdErr) do |line|
1104
+ output_lines.push(line)
1105
+ end
1106
+
1107
+ in_thr = handle_stream($stdin, ExecutionStreams::StdIn) do |line|
1108
+ stdin.puts(line)
1109
+ end
1110
+
1111
+ wait_for_stream_processing
1112
+ sleep 0.1
1113
+ in_thr.kill if in_thr&.alive?
1114
+ end
1115
+
1116
+ ## select output_lines that look like assignment or match other specs
1117
+ #
1118
+ output_lines = process_string_array(
1119
+ output_lines,
1120
+ begin_pattern: @delegate_object.fetch(:output_assignment_begin, nil),
1121
+ end_pattern: @delegate_object.fetch(:output_assignment_end, nil),
1122
+ scan1: @delegate_object.fetch(:output_assignment_match, nil),
1123
+ format1: @delegate_object.fetch(:output_assignment_format, nil)
1124
+ )
1125
+
1126
+ else
1127
+ output_lines = `#{all_code.join("\n")}`.split("\n")
1128
+ end
1129
+
1130
+ unless output_lines
1131
+ HashDelegator.error_handler('all_code eval output_lines is nil', { abort: true })
1132
+ end
1133
+
1134
+ label_format_above = @delegate_object[:shell_code_label_format_above]
1135
+ label_format_below = @delegate_object[:shell_code_label_format_below]
1136
+ block_source = { document_filename: link_state&.document_filename }
1137
+
1138
+ [label_format_above && format(label_format_above,
1139
+ block_source.merge({ block_name: selected[:oname] }))] +
1140
+ output_lines.map do |line|
1141
+ re = Regexp.new(link_block_data.fetch('pattern', '(?<line>.*)'))
1142
+ re.gsub_format(line, link_block_data.fetch('format', '%{line}')) if re =~ line
1143
+ end.compact +
1144
+ [label_format_below && format(label_format_below,
1145
+ block_source.merge({ block_name: selected[:oname] }))]
1146
+ end
1147
+
811
1148
  def link_history_push_and_next(
812
1149
  curr_block_name:, curr_document_filename:,
813
1150
  inherited_block_names:, inherited_dependencies:, inherited_lines:,
@@ -845,7 +1182,7 @@ module MarkdownExec
845
1182
  return
846
1183
  end
847
1184
 
848
- block = block_find(all_blocks, :oname, block_name)
1185
+ block = HashDelegator.block_find(all_blocks, :oname, block_name)
849
1186
  return unless block
850
1187
 
851
1188
  options_state = read_show_options_and_trigger_reuse(block)
@@ -866,7 +1203,7 @@ module MarkdownExec
866
1203
 
867
1204
  ## Handles the file loading and returns the blocks in the file and MDoc instance
868
1205
  #
869
- def mdoc_menu_and_blocks_from_nested_files
1206
+ def mdoc_menu_and_blocks_from_nested_files(link_state)
870
1207
  all_blocks, mdoc = mdoc_and_blocks_from_nested_files
871
1208
 
872
1209
  # recreate menu with new options
@@ -874,8 +1211,9 @@ module MarkdownExec
874
1211
  all_blocks, mdoc = mdoc_and_blocks_from_nested_files if load_auto_blocks(all_blocks)
875
1212
 
876
1213
  menu_blocks = mdoc.fcbs_per_options(@delegate_object)
877
- add_menu_chrome_blocks!(menu_blocks)
878
- delete_consecutive_blank_lines!(menu_blocks) if true ### compress empty lines
1214
+ add_menu_chrome_blocks!(menu_blocks, link_state)
1215
+ ### compress empty lines
1216
+ HashDelegator.delete_consecutive_blank_lines!(menu_blocks) if true
879
1217
  [all_blocks, menu_blocks, mdoc]
880
1218
  end
881
1219
 
@@ -894,7 +1232,7 @@ module MarkdownExec
894
1232
  # @param option_symbol [Symbol] The symbol key for the menu option in the delegate object.
895
1233
  # @return [String] The formatted or original value of the menu option.
896
1234
  def menu_chrome_formatted_option(option_symbol = :menu_option_back_name)
897
- option_value = safeval(@delegate_object.fetch(option_symbol, ''))
1235
+ option_value = HashDelegator.safeval(@delegate_object.fetch(option_symbol, ''))
898
1236
 
899
1237
  if @delegate_object[:menu_chrome_format]
900
1238
  format(@delegate_object[:menu_chrome_format], option_value)
@@ -903,12 +1241,6 @@ module MarkdownExec
903
1241
  end
904
1242
  end
905
1243
 
906
- def merge_lists(*args)
907
- # Filters out nil values, flattens the arrays, and ensures an empty list is returned if no valid lists are provided
908
- merged = args.compact.flatten
909
- merged.empty? ? [] : merged
910
- end
911
-
912
1244
  # If a method is missing, treat it as a key for the @delegate_object.
913
1245
  def method_missing(method_name, *args, &block)
914
1246
  if @delegate_object.respond_to?(method_name)
@@ -921,17 +1253,13 @@ module MarkdownExec
921
1253
  end
922
1254
  end
923
1255
 
924
- def shift_cli_argument!
925
- return false unless @menu_base_options[:input_cli_rest].present?
1256
+ def shift_cli_argument
1257
+ return true unless @menu_base_options[:input_cli_rest].present?
926
1258
 
927
1259
  @cli_block_name = @menu_base_options[:input_cli_rest].shift
928
- # @delegate_object[:input_cli_rest].shift
929
- # p [__LINE__, @cli_block_name, @menu_base_options[:input_cli_rest]]
930
- true
1260
+ false
931
1261
  end
932
1262
 
933
- # private
934
-
935
1263
  def output_color_formatted(data_sym, color_sym)
936
1264
  formatted_string = string_send_color(@delegate_object[data_sym],
937
1265
  color_sym)
@@ -984,29 +1312,38 @@ module MarkdownExec
984
1312
  ), level: level
985
1313
  end
986
1314
 
987
- # private
988
-
989
- def parse_yaml_data_from_body(body)
990
- body.any? ? YAML.load(body.join("\n")) : {}
991
- end
992
-
993
- def pop_add_current_code_to_head_and_trigger_load(_link_state, block_names, code_lines,
994
- dependencies)
1315
+ def pop_add_current_code_to_head_and_trigger_load(link_state, block_names, code_lines,
1316
+ dependencies, selected)
995
1317
  pop = @link_history.pop # updatable
996
- next_link_state = LinkState.new(
997
- block_name: pop.block_name,
998
- document_filename: pop.document_filename,
999
- inherited_block_names:
1000
- (pop.inherited_block_names + block_names).sort.uniq,
1001
- inherited_dependencies:
1002
- dependencies.merge(pop.inherited_dependencies || {}), ### merge, not replace, key data
1003
- inherited_lines:
1004
- code_merge(pop.inherited_lines, code_lines)
1005
- )
1006
- @link_history.push(next_link_state)
1318
+ if pop.document_filename
1319
+ next_state = LinkState.new(
1320
+ block_name: pop.block_name,
1321
+ document_filename: pop.document_filename,
1322
+ inherited_block_names:
1323
+ (pop.inherited_block_names + block_names).sort.uniq,
1324
+ inherited_dependencies:
1325
+ dependencies.merge(pop.inherited_dependencies || {}), ### merge, not replace, key data
1326
+ inherited_lines:
1327
+ HashDelegator.code_merge(pop.inherited_lines, code_lines)
1328
+ )
1329
+ @link_history.push(next_state)
1007
1330
 
1008
- next_link_state.block_name = nil
1009
- LoadFileLinkState.new(LoadFile::Load, next_link_state)
1331
+ next_state.block_name = nil
1332
+ LoadFileLinkState.new(LoadFile::Load, next_state)
1333
+ else
1334
+ # no history exists; must have been called independently => retain script
1335
+ link_history_push_and_next(
1336
+ curr_block_name: selected[:oname],
1337
+ curr_document_filename: @delegate_object[:filename],
1338
+ inherited_block_names: ((link_state&.inherited_block_names || []) + block_names).sort.uniq,
1339
+ inherited_dependencies: (link_state&.inherited_dependencies || {}).merge(dependencies || {}), ### merge, not replace, key data
1340
+ inherited_lines: HashDelegator.code_merge(link_state&.inherited_lines, code_lines),
1341
+ next_block_name: '', # not link_block_data['block'] || ''
1342
+ next_document_filename: @delegate_object[:filename], # not next_document_filename
1343
+ next_load_file: LoadFile::Reuse # not next_document_filename == @delegate_object[:filename] ? LoadFile::Reuse : LoadFile::Load
1344
+ )
1345
+ # LoadFileLinkState.new(LoadFile::Reuse, link_state)
1346
+ end
1010
1347
  end
1011
1348
 
1012
1349
  # This method handles the back-link operation in the Markdown execution context.
@@ -1025,7 +1362,7 @@ module MarkdownExec
1025
1362
  end
1026
1363
 
1027
1364
  def post_execution_process
1028
- initialize_and_save_execution_output
1365
+ do_save_execution_output
1029
1366
  output_execution_summary
1030
1367
  output_execution_result
1031
1368
  end
@@ -1041,7 +1378,7 @@ module MarkdownExec
1041
1378
  %i[block_name_include_match block_name_wrapper_match])
1042
1379
 
1043
1380
  fcb.merge!(
1044
- name: indent_all_lines(fcb.dname, fcb.fetch(:indent, nil)),
1381
+ name: fcb.dname,
1045
1382
  label: BlockLabel.make(
1046
1383
  body: fcb[:body],
1047
1384
  filename: @delegate_object[:filename],
@@ -1071,11 +1408,63 @@ module MarkdownExec
1071
1408
  when :filter
1072
1409
  %i[blocks line]
1073
1410
  when :line
1074
- unless @delegate_object[:no_chrome]
1075
- create_and_add_chrome_blocks(blocks,
1076
- fcb)
1411
+ create_and_add_chrome_blocks(blocks, fcb) unless @delegate_object[:no_chrome]
1412
+ end
1413
+ end
1414
+
1415
+ def process_string_array(arr, begin_pattern: nil, end_pattern: nil, scan1: nil,
1416
+ format1: nil)
1417
+ in_block = !begin_pattern.present?
1418
+ collected_lines = []
1419
+
1420
+ arr.each do |line|
1421
+ if in_block
1422
+ if end_pattern.present? && line.match?(end_pattern)
1423
+ in_block = false
1424
+ elsif scan1.present?
1425
+ if format1.present?
1426
+ caps = extract_named_captures_from_option(line, scan1)
1427
+ if caps
1428
+ formatted = format(format1, caps)
1429
+ collected_lines << formatted
1430
+ end
1431
+ else
1432
+ caps = line.match(scan1)
1433
+ if caps
1434
+ formatted = caps[0]
1435
+ collected_lines << formatted
1436
+ end
1437
+ end
1438
+ else
1439
+ collected_lines << line
1440
+ end
1441
+ elsif begin_pattern.present? && line.match?(begin_pattern)
1442
+ in_block = true
1077
1443
  end
1078
1444
  end
1445
+
1446
+ collected_lines
1447
+ end
1448
+
1449
+ def prompt_approve_repeat
1450
+ sel = @prompt.select(
1451
+ string_send_color(@delegate_object[:prompt_debounce],
1452
+ :prompt_color_after_script_execution),
1453
+ default: @delegate_object[:prompt_no],
1454
+ filter: true,
1455
+ quiet: true
1456
+ ) do |menu|
1457
+ menu.choice @delegate_object[:prompt_yes]
1458
+ menu.choice @delegate_object[:prompt_no]
1459
+ menu.choice @delegate_object[:prompt_uninterrupted]
1460
+ end
1461
+ return false if sel == @delegate_object[:prompt_no]
1462
+ return true if sel == @delegate_object[:prompt_yes]
1463
+
1464
+ @allowed_execution_block = @prior_execution_block
1465
+ true
1466
+ rescue TTY::Reader::InputInterrupt
1467
+ exit 1
1079
1468
  end
1080
1469
 
1081
1470
  ##
@@ -1093,7 +1482,7 @@ module MarkdownExec
1093
1482
  #
1094
1483
  # @return [Boolean] Returns true if the user approves (selects 'Yes'), false otherwise.
1095
1484
  ##
1096
- def prompt_for_user_approval(required_lines)
1485
+ def prompt_for_user_approval(required_lines, selected)
1097
1486
  # Present a selection menu for user approval.
1098
1487
  sel = @prompt.select(
1099
1488
  string_send_color(@delegate_object[:prompt_approve_block],
@@ -1113,7 +1502,7 @@ module MarkdownExec
1113
1502
  if sel == MenuOptions::SCRIPT_TO_CLIPBOARD
1114
1503
  copy_to_clipboard(required_lines)
1115
1504
  elsif sel == MenuOptions::SAVE_SCRIPT
1116
- save_to_file(required_lines)
1505
+ save_to_file(required_lines, selected)
1117
1506
  end
1118
1507
 
1119
1508
  sel == MenuOptions::YES
@@ -1148,12 +1537,13 @@ module MarkdownExec
1148
1537
  # @return [LoadFileLinkState] Object indicating the next action for file loading.
1149
1538
  def push_link_history_and_trigger_load(link_block_body, mdoc, selected,
1150
1539
  link_state = LinkState.new)
1151
- link_block_data = parse_yaml_data_from_body(link_block_body)
1540
+ link_block_data = HashDelegator.parse_yaml_data_from_body(link_block_body)
1152
1541
 
1153
1542
  # load key and values from link block into current environment
1154
1543
  #
1155
1544
  (link_block_data['vars'] || []).each do |(key, value)|
1156
1545
  ENV[key] = value.to_s
1546
+ ### add to inherited_lines
1157
1547
  end
1158
1548
 
1159
1549
  ## collect blocks specified by block
@@ -1175,31 +1565,22 @@ module MarkdownExec
1175
1565
  end
1176
1566
  next_document_filename = link_block_data['file'] || @delegate_object[:filename]
1177
1567
 
1178
- # if an eval link block, evaluate code_lines and return its standard output
1568
+ ## append blocks loaded per LinkDataKeys::Load
1179
1569
  #
1180
- if link_block_data.fetch('eval', false)
1181
- all_code = code_merge(link_state&.inherited_lines, code_lines)
1182
- output = `#{all_code.join("\n")}`.split("\n")
1183
- label_format_above = @delegate_object[:shell_code_label_format_above]
1184
- label_format_below = @delegate_object[:shell_code_label_format_below]
1185
- block_source = { document_filename: link_state&.document_filename }
1186
-
1187
- code_lines = [label_format_above && format(label_format_above,
1188
- block_source.merge({ block_name: selected[:oname] }))] +
1189
- output.map do |line|
1190
- re = Regexp.new(link_block_data.fetch('pattern', '(?<line>.*)'))
1191
- if re =~ line
1192
- re.gsub_format(line, link_block_data.fetch('format', '%{line}'))
1193
- end
1194
- end.compact +
1195
- [label_format_below && format(label_format_below,
1196
- block_source.merge({ block_name: selected[:oname] }))]
1197
-
1570
+ if (load_filespec = link_block_data.fetch(LinkDataKeys::Load, '')).present?
1571
+ code_lines += File.readlines(load_filespec, chomp: true)
1198
1572
  end
1199
1573
 
1200
- if link_block_data['return']
1574
+ # if an eval link block, evaluate code_lines and return its standard output
1575
+ #
1576
+ if link_block_data.fetch(LinkDataKeys::Eval,
1577
+ false) || link_block_data.fetch(LinkDataKeys::Exec, false)
1578
+ code_lines = link_block_data_eval(link_state, code_lines, selected, link_block_data)
1579
+ end
1580
+
1581
+ if link_block_data[LinkDataKeys::Return]
1201
1582
  pop_add_current_code_to_head_and_trigger_load(link_state, block_names, code_lines,
1202
- dependencies)
1583
+ dependencies, selected)
1203
1584
 
1204
1585
  else
1205
1586
  link_history_push_and_next(
@@ -1207,7 +1588,7 @@ module MarkdownExec
1207
1588
  curr_document_filename: @delegate_object[:filename],
1208
1589
  inherited_block_names: ((link_state&.inherited_block_names || []) + block_names).sort.uniq,
1209
1590
  inherited_dependencies: (link_state&.inherited_dependencies || {}).merge(dependencies || {}), ### merge, not replace, key data
1210
- inherited_lines: code_merge(link_state&.inherited_lines, code_lines),
1591
+ inherited_lines: HashDelegator.code_merge(link_state&.inherited_lines, code_lines),
1211
1592
  next_block_name: link_block_data['block'] || '',
1212
1593
  next_document_filename: next_document_filename,
1213
1594
  next_load_file: next_document_filename == @delegate_object[:filename] ? LoadFile::Reuse : LoadFile::Load
@@ -1215,20 +1596,6 @@ module MarkdownExec
1215
1596
  end
1216
1597
  end
1217
1598
 
1218
- # Reads required code blocks from a temporary file specified by an environment variable.
1219
- # @return [Array<String>] Lines read from the temporary file, or an empty array if file is not found or path is empty.
1220
- def read_required_blocks_from_temp_file(temp_blocks_file_path)
1221
- return [] if temp_blocks_file_path.to_s.empty?
1222
-
1223
- if File.exist?(temp_blocks_file_path)
1224
- File.readlines(
1225
- temp_blocks_file_path, chomp: true
1226
- )
1227
- else
1228
- []
1229
- end
1230
- end
1231
-
1232
1599
  def runtime_exception(exception_sym, name, items)
1233
1600
  if @delegate_object[exception_sym] != 0
1234
1601
  data = { name: name, detail: items.join(', ') }
@@ -1248,22 +1615,8 @@ module MarkdownExec
1248
1615
  exit @delegate_object[exception_sym]
1249
1616
  end
1250
1617
 
1251
- def safely_remove_file(path)
1252
- FileUtils.rm_f(path)
1253
- end
1254
-
1255
- # Evaluates the given string as Ruby code and rescues any StandardErrors.
1256
- # If an error occurs, it calls the error_handler method with 'safeval'.
1257
- # @param str [String] The string to be evaluated.
1258
- # @return [Object] The result of evaluating the string.
1259
- def safeval(str)
1260
- eval(str)
1261
- rescue StandardError
1262
- error_handler('safeval')
1263
- end
1264
-
1265
- def save_to_file(required_lines)
1266
- write_command_file(required_lines)
1618
+ def save_to_file(required_lines, selected)
1619
+ write_command_file(required_lines, selected)
1267
1620
  @fout.fout "File saved: #{@run_state.saved_filespec}"
1268
1621
  end
1269
1622
 
@@ -1275,51 +1628,57 @@ module MarkdownExec
1275
1628
  # @return [Nil] Returns nil if no code block is selected or an error occurs.
1276
1629
  def document_menu_loop
1277
1630
  @menu_base_options = @delegate_object
1278
- link_state, block_name_from_cli, now_using_cli = initialize_selection_states
1631
+ link_state = LinkState.new(
1632
+ block_name: @delegate_object[:block_name],
1633
+ document_filename: @delegate_object[:filename]
1634
+ )
1635
+ @run_state.block_name_from_cli = link_state.block_name.present?
1636
+ @cli_block_name = link_state.block_name
1637
+ now_using_cli = @run_state.block_name_from_cli
1279
1638
  menu_default_dname = nil
1280
1639
 
1640
+ @run_state.batch_random = Random.new.rand
1641
+ @run_state.batch_index = 0
1642
+
1281
1643
  loop do
1282
- # @bsp 'loop',block_name_from_cli,@cli_block_name
1283
- block_name_from_cli, now_using_cli, blocks_in_file, menu_blocks, mdoc = \
1284
- set_delobj_menu_loop_vars(block_name_from_cli, now_using_cli, link_state)
1644
+ @run_state.batch_index += 1
1645
+ @run_state.in_own_window = false
1646
+
1647
+ # &bsp 'loop', block_name_from_cli, @cli_block_name
1648
+ @run_state.block_name_from_cli, now_using_cli, blocks_in_file, menu_blocks, mdoc = \
1649
+ set_delobj_menu_loop_vars(@run_state.block_name_from_cli, now_using_cli, link_state)
1285
1650
 
1286
1651
  # cli or user selection
1287
1652
  #
1288
1653
  block_state = load_cli_or_user_selected_block(blocks_in_file, menu_blocks,
1289
1654
  menu_default_dname)
1290
- if block_state.state == MenuState::EXIT
1291
- # @bsp 'MenuState::EXIT -> break'
1655
+ # &bsp '@run_state.block_name_from_cli:',@run_state.block_name_from_cli
1656
+ if !block_state
1657
+ HashDelegator.error_handler('block_state missing', { abort: true })
1658
+ elsif block_state.state == MenuState::EXIT
1659
+ # &bsp 'load_cli_or_user_selected_block -> break'
1292
1660
  break
1293
1661
  end
1294
1662
 
1295
1663
  dump_and_warn_block_state(block_state.block)
1296
1664
  link_state, menu_default_dname = exec_bash_next_state(block_state.block, mdoc,
1297
1665
  link_state)
1298
- if prompt_user_exit(block_name_from_cli, block_state.block)
1299
- # @bsp 'prompt_user_exit -> break'
1666
+ if prompt_user_exit(@run_state.block_name_from_cli, block_state.block)
1667
+ # &bsp 'prompt_user_exit -> break'
1300
1668
  break
1301
1669
  end
1302
1670
 
1303
- link_state.block_name, block_name_from_cli, cli_break = \
1304
- next_state_from_cli(now_using_cli, block_state)
1671
+ link_state.block_name, @run_state.block_name_from_cli, cli_break = \
1672
+ HashDelegator.next_link_state(!shift_cli_argument, now_using_cli, block_state)
1305
1673
 
1306
- if cli_break
1307
- # @bsp 'read_block_name_from_cli + next_link_state -> break'
1674
+ if !block_state.block[:block_name_from_ui] && cli_break
1675
+ # &bsp '!block_name_from_ui + cli_break -> break'
1308
1676
  break
1309
1677
  end
1310
1678
  end
1311
1679
  rescue StandardError
1312
- error_handler('document_menu_loop',
1313
- { abort: true })
1314
- end
1315
-
1316
- def next_state_from_cli(now_using_cli, block_state)
1317
- was_using_cli = now_using_cli
1318
- block_name_from_cli, = read_block_name_from_cli(now_using_cli)
1319
- block_name, block_name_from_cli, cli_break = \
1320
- next_link_state(block_name_from_cli, was_using_cli, block_state)
1321
-
1322
- [block_name, block_name_from_cli, cli_break]
1680
+ HashDelegator.error_handler('document_menu_loop',
1681
+ { abort: true })
1323
1682
  end
1324
1683
 
1325
1684
  def exec_bash_next_state(block_state_block, mdoc, link_state)
@@ -1342,7 +1701,7 @@ module MarkdownExec
1342
1701
 
1343
1702
  # update @delegate_object and @menu_base_options in auto_load
1344
1703
  #
1345
- blocks_in_file, menu_blocks, mdoc = mdoc_menu_and_blocks_from_nested_files
1704
+ blocks_in_file, menu_blocks, mdoc = mdoc_menu_and_blocks_from_nested_files(link_state)
1346
1705
  dump_delobj(blocks_in_file, menu_blocks, link_state)
1347
1706
 
1348
1707
  [block_name_from_cli, now_using_cli, blocks_in_file, menu_blocks, mdoc]
@@ -1358,8 +1717,8 @@ module MarkdownExec
1358
1717
  end
1359
1718
 
1360
1719
  def manage_cli_selection_state(block_name_from_cli, now_using_cli, link_state)
1361
- if block_name_from_cli && @cli_block_name == '.'
1362
- # @bsp 'pause cli control, allow user to select block'
1720
+ if block_name_from_cli && @cli_block_name == @menu_base_options[:menu_persist_block_name]
1721
+ # &bsp 'pause cli control, allow user to select block'
1363
1722
  block_name_from_cli = false
1364
1723
  now_using_cli = false
1365
1724
  @menu_base_options[:block_name] = \
@@ -1373,45 +1732,6 @@ module MarkdownExec
1373
1732
  [block_name_from_cli, now_using_cli]
1374
1733
  end
1375
1734
 
1376
- def next_link_state(block_name_from_cli, was_using_cli, block_state)
1377
- # @bsp 'next_link_state',block_name_from_cli, was_using_cli, block_state
1378
- # Set block_name based on block_name_from_cli
1379
- block_name = block_name_from_cli ? @cli_block_name : nil
1380
-
1381
- # Determine the state of breaker based on was_using_cli and the block type
1382
- breaker = !block_name_from_cli && was_using_cli && block_state.block[:shell] == BlockType::BASH
1383
-
1384
- # Reset block_name_from_cli if the conditions are not met
1385
- block_name_from_cli ||= false
1386
-
1387
- [block_name, block_name_from_cli, breaker]
1388
- end
1389
-
1390
- # Initialize the selection states for the execution loop.
1391
- def initialize_selection_states
1392
- link_state = LinkState.new(
1393
- block_name: @delegate_object[:block_name],
1394
- document_filename: @delegate_object[:filename]
1395
- )
1396
- block_name_from_cli, now_using_cli = handle_cli_block_name(link_state)
1397
- [link_state, block_name_from_cli, now_using_cli]
1398
- end
1399
-
1400
- # Update the state related to CLI block name.
1401
- #
1402
- # This method updates the flags indicating whether a CLI block name is being used
1403
- # and if it was being used previously.
1404
- #
1405
- # @param block_name_from_cli [Boolean] Indicates if the block name is from CLI.
1406
- # @return [Array] Returns the updated state of block name from CLI and its usage.
1407
- def read_block_name_from_cli(block_name_from_cli)
1408
- # was_using_cli = block_name_from_cli
1409
- block_name_from_cli = shift_cli_argument!
1410
- now_using_cli = block_name_from_cli
1411
-
1412
- [block_name_from_cli, now_using_cli]
1413
- end
1414
-
1415
1735
  # Update the block name in the link state and delegate object.
1416
1736
  #
1417
1737
  # This method updates the block name based on whether it was specified
@@ -1425,21 +1745,6 @@ module MarkdownExec
1425
1745
  block_name_from_cli ? @cli_block_name : link_state.block_name
1426
1746
  end
1427
1747
 
1428
- # Handle CLI block name and determine the current CLI usage state.
1429
- #
1430
- # This method processes the CLI block name from the link state and sets
1431
- # the initial state for CLI usage.
1432
- #
1433
- # @param link_state [LinkState] The current link state object.
1434
- # @return [Array] Returns the state of block name from CLI and current usage of CLI.
1435
- def handle_cli_block_name(link_state)
1436
- block_name_from_cli = link_state.block_name.present?
1437
- @cli_block_name = link_state.block_name
1438
- now_using_cli = block_name_from_cli
1439
-
1440
- [block_name_from_cli, now_using_cli]
1441
- end
1442
-
1443
1748
  # Outputs warnings based on the delegate object's configuration
1444
1749
  #
1445
1750
  # @param delegate_object [Hash] The delegate object containing configuration flags.
@@ -1482,11 +1787,16 @@ module MarkdownExec
1482
1787
  selection = @prompt.select(prompt_text,
1483
1788
  names,
1484
1789
  opts.merge(filter: true))
1790
+
1485
1791
  item = if names.first.instance_of?(String)
1486
1792
  { dname: selection }
1487
1793
  else
1488
1794
  names.find { |item| item[:dname] == selection }
1489
1795
  end
1796
+ unless item
1797
+ HashDelegator.error_handler('select_option_with_metadata', error: 'menu item not found')
1798
+ exit 1
1799
+ end
1490
1800
 
1491
1801
  item.merge(
1492
1802
  if selection == menu_chrome_colored_option(:menu_option_back_name)
@@ -1500,11 +1810,11 @@ module MarkdownExec
1500
1810
  rescue TTY::Reader::InputInterrupt
1501
1811
  exit 1
1502
1812
  rescue StandardError
1503
- error_handler('select_option_with_metadata')
1813
+ HashDelegator.error_handler('select_option_with_metadata')
1504
1814
  end
1505
1815
 
1506
1816
  def set_environment_variables_for_block(selected)
1507
- YAML.load(selected[:body].join("\n")).each do |key, value|
1817
+ YAML.load(selected[:body].join("\n"))&.each do |key, value|
1508
1818
  ENV[key] = value.to_s
1509
1819
  next unless @delegate_object[:menu_vars_set_format].present?
1510
1820
 
@@ -1514,22 +1824,8 @@ module MarkdownExec
1514
1824
  end
1515
1825
  end
1516
1826
 
1517
- def set_environment_variables_per_array(vars)
1518
- vars ||= []
1519
- vars.each { |key, value| ENV[key] = value.to_s }
1520
- end
1521
-
1522
- def set_file_permissions(file_path, chmod_value)
1523
- File.chmod(chmod_value, file_path)
1524
- end
1525
-
1526
- def set_script_block_name(selected)
1527
- @run_state.script_block_name = selected[:oname]
1528
- end
1529
-
1530
1827
  def should_add_back_option?
1531
1828
  @delegate_object[:menu_with_back] && @link_history.prior_state_exist?
1532
- # @delegate_object[:menu_with_back] && link_history_prior_state_exist?
1533
1829
  end
1534
1830
 
1535
1831
  # Initializes a new fenced code block (FCB) object based on the provided line and heading information.
@@ -1547,13 +1843,22 @@ module MarkdownExec
1547
1843
  !name.match(Regexp.new(@delegate_object[:block_name_wrapper_match]))
1548
1844
  end
1549
1845
 
1846
+ dname = oname = title = ''
1847
+ nickname = nil
1848
+ if @delegate_object[:block_name_nick_match].present? && oname =~ Regexp.new(@delegate_object[:block_name_nick_match])
1849
+ nickname = $~[0]
1850
+ else
1851
+ dname = oname = title = fcb_title_groups.fetch(:name, '')
1852
+ end
1853
+
1550
1854
  MarkdownExec::FCB.new(
1551
1855
  body: [],
1552
1856
  call: rest.match(Regexp.new(@delegate_object[:block_calls_scan]))&.to_a&.first,
1553
- dname: fcb_title_groups.fetch(:name, ''),
1857
+ dname: dname,
1554
1858
  headings: headings,
1555
1859
  indent: fcb_title_groups.fetch(:indent, ''),
1556
- oname: fcb_title_groups.fetch(:name, ''),
1860
+ nickname: nickname,
1861
+ oname: oname,
1557
1862
  reqs: reqs,
1558
1863
  shell: fcb_title_groups.fetch(:shell, ''),
1559
1864
  stdin: if (tn = rest.match(/<(?<type>\$)?(?<name>[A-Za-z_-]\S+)/))
@@ -1562,7 +1867,7 @@ module MarkdownExec
1562
1867
  stdout: if (tn = rest.match(/>(?<type>\$)?(?<name>[A-Za-z_\-.\w]+)/))
1563
1868
  tn.named_captures.sym_keys
1564
1869
  end,
1565
- title: fcb_title_groups.fetch(:name, ''),
1870
+ title: title,
1566
1871
  wraps: wraps
1567
1872
  )
1568
1873
  end
@@ -1573,44 +1878,8 @@ module MarkdownExec
1573
1878
  # @param color_sym [Symbol] The symbol representing the color method.
1574
1879
  # @param default [String] Default color method to use if color_sym is not found in @delegate_object.
1575
1880
  # @return [String] The string with the applied color method.
1576
- def string_send_color(string, color_sym, default: 'plain')
1577
- color_method = @delegate_object.fetch(color_sym, default).to_sym
1578
- string.to_s.send(color_method)
1579
- end
1580
-
1581
- # Creates a TTY prompt with custom settings. Specifically, it disables the default 'cross' symbol and
1582
- # defines a lambda function to handle interrupts.
1583
- # @return [TTY::Prompt] A new TTY::Prompt instance with specified configurations.
1584
- def tty_prompt_without_disabled_symbol
1585
- TTY::Prompt.new(
1586
- interrupt: lambda {
1587
- puts
1588
- raise TTY::Reader::InputInterrupt
1589
- },
1590
- symbols: { cross: ' ' }
1591
- )
1592
- end
1593
-
1594
- # Updates the hierarchy of document headings based on the given line.
1595
- # Utilizes regular expressions to identify heading levels.
1596
- # @param line [String] The line of text to check for headings.
1597
- # @param headings [Array<String>] Current headings hierarchy.
1598
- # @return [Array<String>] Updated headings hierarchy.
1599
- def update_document_headings(line, headings)
1600
- heading3_match = Regexp.new(@delegate_object[:heading3_match])
1601
- heading2_match = Regexp.new(@delegate_object[:heading2_match])
1602
- heading1_match = Regexp.new(@delegate_object[:heading1_match])
1603
-
1604
- case line
1605
- when heading3_match
1606
- [headings[0], headings[1], $~[:name]]
1607
- when heading2_match
1608
- [headings[0], $~[:name]]
1609
- when heading1_match
1610
- [$~[:name]]
1611
- else
1612
- headings
1613
- end
1881
+ def string_send_color(string, color_sym)
1882
+ HashDelegator.apply_color_from_hash(string, @delegate_object, color_sym)
1614
1883
  end
1615
1884
 
1616
1885
  ##
@@ -1635,16 +1904,12 @@ module MarkdownExec
1635
1904
  def update_line_and_block_state(nested_line, state, selected_messages,
1636
1905
  &block)
1637
1906
  line = nested_line.to_s
1638
- if @delegate_object[:menu_blocks_with_headings]
1639
- state[:headings] = update_document_headings(line, state[:headings])
1640
- end
1641
-
1642
1907
  if line.match(@delegate_object[:fenced_start_and_end_regex])
1643
1908
  if state[:in_fenced_block]
1644
1909
  ## end of code block
1645
1910
  #
1646
- update_menu_attrib_yield_selected(state[:fcb], selected_messages,
1647
- &block)
1911
+ HashDelegator.update_menu_attrib_yield_selected(state[:fcb], selected_messages, @delegate_object,
1912
+ &block)
1648
1913
  state[:in_fenced_block] = false
1649
1914
  else
1650
1915
  ## start of code block
@@ -1665,29 +1930,14 @@ module MarkdownExec
1665
1930
  elsif nested_line[:depth].zero? || @delegate_object[:menu_include_imported_notes]
1666
1931
  # add line if it is depth 0 or option allows it
1667
1932
  #
1668
- yield_line_if_selected(line, selected_messages, &block)
1933
+ HashDelegator.yield_line_if_selected(line, selected_messages, &block)
1669
1934
 
1670
1935
  else
1671
- # @bsp 'line is not recognized for block state'
1936
+ # &bsp 'line is not recognized for block state'
1672
1937
 
1673
1938
  end
1674
1939
  end
1675
1940
 
1676
- # Updates the attributes of the given fcb object and conditionally yields to a block.
1677
- # It initializes fcb names and sets the default block title from fcb's body.
1678
- # If the fcb has a body and meets certain conditions, it yields to the given block.
1679
- #
1680
- # @param fcb [Object] The fcb object whose attributes are to be updated.
1681
- # @param selected_messages [Array<Symbol>] A list of message types to determine if yielding is applicable.
1682
- # @param block [Block] An optional block to yield to if conditions are met.
1683
- def update_menu_attrib_yield_selected(fcb, selected_messages, &block)
1684
- initialize_fcb_names(fcb)
1685
- return unless fcb.body
1686
-
1687
- default_block_title_from_body(fcb)
1688
- yield_to_block_if_applicable(fcb, selected_messages, &block)
1689
- end
1690
-
1691
1941
  # Processes YAML data from the selected menu item, updating delegate objects and optionally printing formatted output.
1692
1942
  # @param selected [Hash] Selected item from the menu containing a YAML body.
1693
1943
  # @param tgt2 [Hash, nil] An optional target hash to update with YAML data.
@@ -1717,10 +1967,10 @@ module MarkdownExec
1717
1967
 
1718
1968
  def wait_for_user_selected_block(all_blocks, menu_blocks, default)
1719
1969
  block_state = wait_for_user_selection(all_blocks, menu_blocks, default)
1720
- handle_block_state(block_state)
1970
+ handle_back_or_continue(block_state)
1721
1971
  block_state
1722
1972
  rescue StandardError
1723
- error_handler('wait_for_user_selected_block')
1973
+ HashDelegator.error_handler('wait_for_user_selected_block')
1724
1974
  end
1725
1975
 
1726
1976
  def wait_for_user_selection(_all_blocks, menu_blocks, default)
@@ -1746,15 +1996,17 @@ module MarkdownExec
1746
1996
  end
1747
1997
 
1748
1998
  # Handles the core logic for generating the command file's metadata and content.
1749
- def write_command_file(required_lines)
1999
+ def write_command_file(required_lines, selected)
1750
2000
  return unless @delegate_object[:save_executed_script]
1751
2001
 
1752
2002
  time_now = Time.now.utc
1753
2003
  @run_state.saved_script_filename =
1754
- SavedAsset.script_name(blockname: @delegate_object[:block_name],
1755
- filename: @delegate_object[:filename],
1756
- prefix: @delegate_object[:saved_script_filename_prefix],
1757
- time: time_now)
2004
+ SavedAsset.script_name(
2005
+ blockname: selected[:nickname] || selected[:oname],
2006
+ filename: @delegate_object[:filename],
2007
+ prefix: @delegate_object[:saved_script_filename_prefix],
2008
+ time: time_now
2009
+ )
1758
2010
  @run_state.saved_filespec =
1759
2011
  File.join(@delegate_object[:saved_script_folder],
1760
2012
  @run_state.saved_script_filename)
@@ -1771,32 +2023,13 @@ module MarkdownExec
1771
2023
  "# time: #{time_now}\n" \
1772
2024
  "#{required_lines.flatten.join("\n")}\n"
1773
2025
 
1774
- create_file_and_write_string_with_permissions(
2026
+ HashDelegator.create_file_and_write_string_with_permissions(
1775
2027
  @run_state.saved_filespec,
1776
2028
  content,
1777
2029
  @delegate_object[:saved_script_chmod]
1778
2030
  )
1779
2031
  rescue StandardError
1780
- error_handler('write_command_file')
1781
- end
1782
-
1783
- def save_executed_script_if_specified(lines)
1784
- write_command_file(lines) if @delegate_object[:save_executed_script]
1785
- end
1786
-
1787
- def write_execution_output_to_file
1788
- FileUtils.mkdir_p File.dirname(@delegate_object[:logged_stdout_filespec])
1789
-
1790
- File.write(
1791
- @delegate_object[:logged_stdout_filespec],
1792
- ["-STDOUT-\n",
1793
- format_execution_streams(ExecutionStreams::StdOut),
1794
- "-STDERR-\n",
1795
- format_execution_streams(ExecutionStreams::StdErr),
1796
- "-STDIN-\n",
1797
- format_execution_streams(ExecutionStreams::StdIn),
1798
- "\n"].join
1799
- )
2032
+ HashDelegator.error_handler('write_command_file')
1800
2033
  end
1801
2034
 
1802
2035
  # Writes required code blocks to a temporary file and sets an environment variable with its path.
@@ -1814,912 +2047,858 @@ module MarkdownExec
1814
2047
  []
1815
2048
  end
1816
2049
 
1817
- code_blocks = (read_required_blocks_from_temp_file(import_filename) +
2050
+ code_blocks = (HashDelegator.read_required_blocks_from_temp_file(import_filename) +
1818
2051
  c1).join("\n")
1819
2052
 
1820
- write_code_to_file(code_blocks, temp_file_path)
2053
+ HashDelegator.write_code_to_file(code_blocks, temp_file_path)
1821
2054
  end
2055
+ end
2056
+ end
2057
+
2058
+ return if $PROGRAM_NAME != __FILE__
2059
+
2060
+ require 'bundler/setup'
2061
+ Bundler.require(:default)
2062
+
2063
+ require 'minitest/autorun'
2064
+ require 'mocha/minitest'
1822
2065
 
1823
- # Writes the provided code blocks to a file.
1824
- # @param code_blocks [String] Code blocks to write into the file.
1825
- def write_code_to_file(content, path)
1826
- File.write(path, content)
2066
+ module MarkdownExec
2067
+ class TestHashDelegator < Minitest::Test
2068
+ def setup
2069
+ @hd = HashDelegator.new
2070
+ @mdoc = mock('MarkdownDocument')
2071
+ end
2072
+
2073
+ def test_calling_execute_required_lines_calls_command_execute_with_argument_args_value
2074
+ pigeon = 'E'
2075
+ obj = {
2076
+ output_execution_label_format: '',
2077
+ output_execution_label_name_color: 'plain',
2078
+ output_execution_label_value_color: 'plain'
2079
+ }
2080
+
2081
+ c = MarkdownExec::HashDelegator.new(obj)
2082
+ c.pass_args = pigeon
2083
+
2084
+ # Expect that method opts_command_execute is called with argument args having value pigeon
2085
+ c.expects(:command_execute).with(
2086
+ '',
2087
+ args: pigeon
2088
+ )
2089
+
2090
+ # Call method opts_execute_required_lines
2091
+ c.execute_required_lines
1827
2092
  end
1828
2093
 
1829
- # Yields a line as a new block if the selected message type includes :line.
1830
- # @param [String] line The line to be processed.
1831
- # @param [Array<Symbol>] selected_messages A list of message types to check.
1832
- # @param [Proc] block The block to be called with the line data.
1833
- def yield_line_if_selected(line, selected_messages, &block)
1834
- return unless block && selected_messages.include?(:line)
2094
+ # Test case for empty body
2095
+ def test_push_link_history_and_trigger_load_with_empty_body
2096
+ assert_equal LoadFile::Reuse,
2097
+ @hd.push_link_history_and_trigger_load([], nil, FCB.new).load_file
2098
+ end
1835
2099
 
1836
- block.call(:line, FCB.new(body: [line]))
2100
+ # Test case for non-empty body without 'file' key
2101
+ def test_push_link_history_and_trigger_load_without_file_key
2102
+ body = ["vars:\n KEY: VALUE"]
2103
+ assert_equal LoadFile::Reuse,
2104
+ @hd.push_link_history_and_trigger_load(body, nil, FCB.new).load_file
1837
2105
  end
1838
2106
 
1839
- # Yields to the provided block with specified parameters if certain conditions are met.
1840
- # The method checks if a block is given, if the selected_messages include :blocks,
1841
- # and if the fcb_select? method from MarkdownExec::Filter returns true for the given fcb.
1842
- #
1843
- # @param fcb [Object] The object to be evaluated and potentially passed to the block.
1844
- # @param selected_messages [Array<Symbol>] A collection of message types, one of which must be :blocks.
1845
- # @param block [Block] A block to be called if conditions are met.
1846
- def yield_to_block_if_applicable(fcb, selected_messages, &block)
1847
- if block_given? && selected_messages.include?(:blocks) &&
1848
- MarkdownExec::Filter.fcb_select?(@delegate_object, fcb)
1849
- block.call :blocks, fcb
1850
- end
2107
+ # Test case for non-empty body with 'file' key
2108
+ def test_push_link_history_and_trigger_load_with_file_key
2109
+ body = ["file: sample_file\nblock: sample_block\nvars:\n KEY: VALUE"]
2110
+ expected_result = LoadFileLinkState.new(LoadFile::Load,
2111
+ LinkState.new(block_name: 'sample_block',
2112
+ document_filename: 'sample_file',
2113
+ inherited_dependencies: {},
2114
+ inherited_lines: []))
2115
+ assert_equal expected_result,
2116
+ @hd.push_link_history_and_trigger_load(body, nil, FCB.new(block_name: 'sample_block',
2117
+ filename: 'sample_file'))
1851
2118
  end
1852
- end
1853
- end
1854
2119
 
1855
- if $PROGRAM_NAME == __FILE__
1856
- require 'bundler/setup'
1857
- Bundler.require(:default)
2120
+ def test_indent_all_lines_with_indent
2121
+ body = "Line 1\nLine 2"
2122
+ indent = ' ' # Two spaces
2123
+ expected_result = " Line 1\n Line 2"
2124
+ assert_equal expected_result, HashDelegator.indent_all_lines(body, indent)
2125
+ end
1858
2126
 
1859
- require 'minitest/autorun'
1860
- require 'mocha/minitest'
2127
+ def test_indent_all_lines_without_indent
2128
+ body = "Line 1\nLine 2"
2129
+ indent = nil
1861
2130
 
1862
- module MarkdownExec
1863
- class TestHashDelegator < Minitest::Test
1864
- def setup
1865
- @hd = HashDelegator.new
1866
- @mdoc = mock('MarkdownDocument')
1867
- end
2131
+ assert_equal body, HashDelegator.indent_all_lines(body, indent)
2132
+ end
1868
2133
 
1869
- def test_calling_execute_required_lines_calls_command_execute_with_argument_args_value
1870
- pigeon = 'E'
1871
- obj = {
1872
- output_execution_label_format: '',
1873
- output_execution_label_name_color: 'plain',
1874
- output_execution_label_value_color: 'plain'
1875
- }
2134
+ def test_indent_all_lines_with_empty_indent
2135
+ body = "Line 1\nLine 2"
2136
+ indent = ''
1876
2137
 
1877
- c = MarkdownExec::HashDelegator.new(obj)
1878
- c.pass_args = pigeon
2138
+ assert_equal body, HashDelegator.indent_all_lines(body, indent)
2139
+ end
1879
2140
 
1880
- # Expect that method opts_command_execute is called with argument args having value pigeon
1881
- c.expects(:command_execute).with(
1882
- '',
1883
- args: pigeon
1884
- )
2141
+ def test_safeval_successful_evaluation
2142
+ assert_equal 4, HashDelegator.safeval('2 + 2')
2143
+ end
1885
2144
 
1886
- # Call method opts_execute_required_lines
1887
- c.execute_required_lines([])
1888
- end
2145
+ def test_safeval_rescue_from_error
2146
+ HashDelegator.stubs(:error_handler).with('safeval')
2147
+ assert_nil HashDelegator.safeval('invalid code')
2148
+ end
1889
2149
 
1890
- # Test case for empty body
1891
- def test_push_link_history_and_trigger_load_with_empty_body
1892
- assert_equal LoadFile::Reuse,
1893
- @hd.push_link_history_and_trigger_load([], nil, FCB.new).load_file
1894
- end
2150
+ def test_set_fcb_title
2151
+ # sample input and output data for testing default_block_title_from_body method
2152
+ input_output_data = [
2153
+ {
2154
+ input: MarkdownExec::FCB.new(title: nil,
2155
+ body: ["puts 'Hello, world!'"]),
2156
+ output: "puts 'Hello, world!'"
2157
+ },
2158
+ {
2159
+ input: MarkdownExec::FCB.new(title: '',
2160
+ body: ['def add(x, y)',
2161
+ ' x + y', 'end']),
2162
+ output: "def add(x, y)\n x + y\n end\n"
2163
+ },
2164
+ {
2165
+ input: MarkdownExec::FCB.new(title: 'foo', body: %w[bar baz]),
2166
+ output: 'foo' # expect the title to remain unchanged
2167
+ }
2168
+ ]
1895
2169
 
1896
- # Test case for non-empty body without 'file' key
1897
- def test_push_link_history_and_trigger_load_without_file_key
1898
- body = ["vars:\n KEY: VALUE"]
1899
- assert_equal LoadFile::Reuse,
1900
- @hd.push_link_history_and_trigger_load(body, nil, FCB.new).load_file
2170
+ # iterate over the input and output data and
2171
+ # assert that the method sets the title as expected
2172
+ input_output_data.each do |data|
2173
+ input = data[:input]
2174
+ output = data[:output]
2175
+ HashDelegator.default_block_title_from_body(input)
2176
+ assert_equal output, input.title
1901
2177
  end
2178
+ end
1902
2179
 
1903
- # Test case for non-empty body with 'file' key
1904
- def test_push_link_history_and_trigger_load_with_file_key
1905
- body = ["file: sample_file\nblock: sample_block\nvars:\n KEY: VALUE"]
1906
- expected_result = LoadFileLinkState.new(LoadFile::Load,
1907
- LinkState.new(block_name: 'sample_block',
1908
- document_filename: 'sample_file',
1909
- inherited_dependencies: {},
1910
- inherited_lines: []))
1911
- assert_equal expected_result,
1912
- @hd.push_link_history_and_trigger_load(body, nil, FCB.new(block_name: 'sample_block',
1913
- filename: 'sample_file'))
2180
+ class TestHashDelegatorAppendDivider < Minitest::Test
2181
+ def setup
2182
+ @hd = HashDelegator.new
2183
+ @hd.instance_variable_set(:@delegate_object, {
2184
+ menu_divider_format: 'Format',
2185
+ menu_initial_divider: 'Initial Divider',
2186
+ menu_final_divider: 'Final Divider',
2187
+ menu_divider_color: :color
2188
+ })
2189
+ @hd.stubs(:string_send_color).returns('Formatted Divider')
2190
+ HashDelegator.stubs(:safeval).returns('Safe Value')
1914
2191
  end
1915
2192
 
1916
- def test_indent_all_lines_with_indent
1917
- body = "Line 1\nLine 2"
1918
- indent = ' ' # Two spaces
1919
- expected_result = " Line 1\n Line 2"
1920
- assert_equal expected_result, @hd.indent_all_lines(body, indent)
2193
+ def test_append_divider_initial
2194
+ menu_blocks = []
2195
+ @hd.append_divider(menu_blocks, :initial)
2196
+
2197
+ assert_equal 1, menu_blocks.size
2198
+ assert_equal 'Formatted Divider', menu_blocks.first.dname
1921
2199
  end
1922
2200
 
1923
- def test_indent_all_lines_without_indent
1924
- body = "Line 1\nLine 2"
1925
- indent = nil
2201
+ def test_append_divider_final
2202
+ menu_blocks = []
2203
+ @hd.append_divider(menu_blocks, :final)
1926
2204
 
1927
- assert_equal body, @hd.indent_all_lines(body, indent)
2205
+ assert_equal 1, menu_blocks.size
2206
+ assert_equal 'Formatted Divider', menu_blocks.last.dname
1928
2207
  end
1929
2208
 
1930
- def test_indent_all_lines_with_empty_indent
1931
- body = "Line 1\nLine 2"
1932
- indent = ''
2209
+ def test_append_divider_without_format
2210
+ @hd.instance_variable_set(:@delegate_object, {})
2211
+ menu_blocks = []
2212
+ @hd.append_divider(menu_blocks, :initial)
1933
2213
 
1934
- assert_equal body, @hd.indent_all_lines(body, indent)
2214
+ assert_empty menu_blocks
1935
2215
  end
2216
+ end
1936
2217
 
1937
- def test_safeval_successful_evaluation
1938
- assert_equal 4, @hd.safeval('2 + 2')
2218
+ class TestHashDelegatorBlockFind < Minitest::Test
2219
+ def setup
2220
+ @hd = HashDelegator.new
1939
2221
  end
1940
2222
 
1941
- def test_safeval_rescue_from_error
1942
- @hd.stubs(:error_handler).with('safeval')
1943
- assert_nil @hd.safeval('invalid code')
2223
+ def test_block_find_with_match
2224
+ blocks = [{ key: 'value1' }, { key: 'value2' }]
2225
+ result = HashDelegator.block_find(blocks, :key, 'value1')
2226
+ assert_equal({ key: 'value1' }, result)
1944
2227
  end
1945
2228
 
1946
- def test_set_fcb_title
1947
- # sample input and output data for testing default_block_title_from_body method
1948
- input_output_data = [
1949
- {
1950
- input: MarkdownExec::FCB.new(title: nil,
1951
- body: ["puts 'Hello, world!'"]),
1952
- output: "puts 'Hello, world!'"
1953
- },
1954
- {
1955
- input: MarkdownExec::FCB.new(title: '',
1956
- body: ['def add(x, y)',
1957
- ' x + y', 'end']),
1958
- output: "def add(x, y)\n x + y\n end\n"
1959
- },
1960
- {
1961
- input: MarkdownExec::FCB.new(title: 'foo', body: %w[bar baz]),
1962
- output: 'foo' # expect the title to remain unchanged
1963
- }
1964
- ]
1965
-
1966
- # iterate over the input and output data and
1967
- # assert that the method sets the title as expected
1968
- input_output_data.each do |data|
1969
- input = data[:input]
1970
- output = data[:output]
1971
- @hd.default_block_title_from_body(input)
1972
- assert_equal output, input.title
1973
- end
2229
+ def test_block_find_without_match
2230
+ blocks = [{ key: 'value1' }, { key: 'value2' }]
2231
+ result = HashDelegator.block_find(blocks, :key, 'value3')
2232
+ assert_nil result
1974
2233
  end
1975
2234
 
1976
- class TestHashDelegatorAppendDivider < Minitest::Test
1977
- def setup
1978
- @hd = HashDelegator.new
1979
- @hd.instance_variable_set(:@delegate_object, {
1980
- menu_divider_format: 'Format',
1981
- menu_initial_divider: 'Initial Divider',
1982
- menu_final_divider: 'Final Divider',
1983
- menu_divider_color: :color
1984
- })
1985
- @hd.stubs(:string_send_color).returns('Formatted Divider')
1986
- @hd.stubs(:safeval).returns('Safe Value')
1987
- end
1988
-
1989
- def test_append_divider_initial
1990
- menu_blocks = []
1991
- @hd.append_divider(menu_blocks, :initial)
1992
-
1993
- assert_equal 1, menu_blocks.size
1994
- assert_equal 'Formatted Divider', menu_blocks.first.dname
1995
- end
1996
-
1997
- def test_append_divider_final
1998
- menu_blocks = []
1999
- @hd.append_divider(menu_blocks, :final)
2000
-
2001
- assert_equal 1, menu_blocks.size
2002
- assert_equal 'Formatted Divider', menu_blocks.last.dname
2003
- end
2004
-
2005
- def test_append_divider_without_format
2006
- @hd.instance_variable_set(:@delegate_object, {})
2007
- menu_blocks = []
2008
- @hd.append_divider(menu_blocks, :initial)
2009
-
2010
- assert_empty menu_blocks
2011
- end
2235
+ def test_block_find_with_default
2236
+ blocks = [{ key: 'value1' }, { key: 'value2' }]
2237
+ result = HashDelegator.block_find(blocks, :key, 'value3', 'default')
2238
+ assert_equal 'default', result
2012
2239
  end
2240
+ end
2013
2241
 
2014
- class TestHashDelegatorBlockFind < Minitest::Test
2015
- def setup
2016
- @hd = HashDelegator.new
2017
- end
2018
-
2019
- def test_block_find_with_match
2020
- blocks = [{ key: 'value1' }, { key: 'value2' }]
2021
- result = @hd.block_find(blocks, :key, 'value1')
2022
- assert_equal({ key: 'value1' }, result)
2023
- end
2024
-
2025
- def test_block_find_without_match
2026
- blocks = [{ key: 'value1' }, { key: 'value2' }]
2027
- result = @hd.block_find(blocks, :key, 'value3')
2028
- assert_nil result
2029
- end
2030
-
2031
- def test_block_find_with_default
2032
- blocks = [{ key: 'value1' }, { key: 'value2' }]
2033
- result = @hd.block_find(blocks, :key, 'value3', 'default')
2034
- assert_equal 'default', result
2035
- end
2242
+ class TestHashDelegatorBlocksFromNestedFiles < Minitest::Test
2243
+ def setup
2244
+ @hd = HashDelegator.new
2245
+ @hd.stubs(:iter_blocks_from_nested_files).yields(:blocks, FCB.new)
2246
+ @hd.stubs(:get_block_summary).returns(FCB.new)
2247
+ @hd.stubs(:create_and_add_chrome_blocks)
2248
+ @hd.instance_variable_set(:@delegate_object, {})
2249
+ HashDelegator.stubs(:error_handler)
2036
2250
  end
2037
2251
 
2038
- class TestHashDelegatorBlocksFromNestedFiles < Minitest::Test
2039
- def setup
2040
- @hd = HashDelegator.new
2041
- @hd.stubs(:iter_blocks_from_nested_files).yields(:blocks, FCB.new)
2042
- @hd.stubs(:get_block_summary).returns(FCB.new)
2043
- @hd.stubs(:create_and_add_chrome_blocks)
2044
- @hd.instance_variable_set(:@delegate_object, {})
2045
- @hd.stubs(:error_handler)
2046
- end
2047
-
2048
- def test_blocks_from_nested_files
2049
- result = @hd.blocks_from_nested_files
2050
-
2051
- assert_kind_of Array, result
2052
- assert_kind_of FCB, result.first
2053
- end
2054
-
2055
- def test_blocks_from_nested_files_with_no_chrome
2056
- @hd.instance_variable_set(:@delegate_object, { no_chrome: true })
2057
- @hd.expects(:create_and_add_chrome_blocks).never
2252
+ def test_blocks_from_nested_files
2253
+ result = @hd.blocks_from_nested_files
2058
2254
 
2059
- result = @hd.blocks_from_nested_files
2060
-
2061
- assert_kind_of Array, result
2062
- end
2255
+ assert_kind_of Array, result
2256
+ assert_kind_of FCB, result.first
2063
2257
  end
2064
2258
 
2065
- class TestHashDelegatorCollectRequiredCodeLines < Minitest::Test
2066
- def setup
2067
- @hd = HashDelegator.new
2068
- @hd.instance_variable_set(:@delegate_object, {})
2069
- @mdoc = mock('YourMDocClass')
2070
- @selected = { shell: BlockType::VARS, body: ['key: value'] }
2071
- @hd.stubs(:read_required_blocks_from_temp_file).returns([])
2072
- @hd.stubs(:string_send_color)
2073
- @hd.stubs(:print)
2074
- end
2259
+ def test_blocks_from_nested_files_with_no_chrome
2260
+ @hd.instance_variable_set(:@delegate_object, { no_chrome: true })
2261
+ @hd.expects(:create_and_add_chrome_blocks).never
2075
2262
 
2076
- def test_collect_required_code_lines_with_vars
2077
- YAML.stubs(:load).returns({ 'key' => 'value' })
2078
- @mdoc.stubs(:collect_recursively_required_code).returns({ code: ['code line'] })
2079
- result = @hd.collect_required_code_lines(@mdoc, @selected, block_source: {})
2263
+ result = @hd.blocks_from_nested_files
2080
2264
 
2081
- assert_equal ['code line'], result
2082
- end
2265
+ assert_kind_of Array, result
2083
2266
  end
2267
+ end
2084
2268
 
2085
- class TestHashDelegatorCommandOrUserSelectedBlock < Minitest::Test
2086
- def setup
2087
- @hd = HashDelegator.new
2088
- @hd.instance_variable_set(:@delegate_object, {})
2089
- @hd.stubs(:error_handler)
2090
- @hd.stubs(:wait_for_user_selected_block)
2091
- end
2092
-
2093
- def test_command_selected_block
2094
- all_blocks = [{ oname: 'block1' }, { oname: 'block2' }]
2095
- @hd.instance_variable_set(:@delegate_object,
2096
- { block_name: 'block1' })
2097
-
2098
- result = @hd.load_cli_or_user_selected_block(all_blocks, [], nil)
2099
-
2100
- assert_equal all_blocks.first, result.block
2101
- assert_nil result.state
2102
- end
2103
-
2104
- def test_user_selected_block
2105
- block_state = SelectedBlockMenuState.new({ oname: 'block2' },
2106
- :some_state)
2107
- @hd.stubs(:wait_for_user_selected_block).returns(block_state)
2269
+ class TestHashDelegatorCollectRequiredCodeLines < Minitest::Test
2270
+ def setup
2271
+ @hd = HashDelegator.new
2272
+ @hd.instance_variable_set(:@delegate_object, {})
2273
+ @mdoc = mock('YourMDocClass')
2274
+ @selected = { shell: BlockType::VARS, body: ['key: value'] }
2275
+ HashDelegator.stubs(:read_required_blocks_from_temp_file).returns([])
2276
+ @hd.stubs(:string_send_color)
2277
+ @hd.stubs(:print)
2278
+ end
2108
2279
 
2109
- result = @hd.load_cli_or_user_selected_block([], [], nil)
2280
+ def test_collect_required_code_lines_with_vars
2281
+ YAML.stubs(:load).returns({ 'key' => 'value' })
2282
+ @mdoc.stubs(:collect_recursively_required_code).returns({ code: ['code line'] })
2283
+ result = @hd.collect_required_code_lines(@mdoc, @selected, block_source: {})
2110
2284
 
2111
- assert_equal block_state.block, result.block
2112
- assert_equal :some_state, result.state
2113
- end
2285
+ assert_equal ['code line'], result
2114
2286
  end
2287
+ end
2115
2288
 
2116
- class TestHashDelegatorCountBlockInFilename < Minitest::Test
2117
- def setup
2118
- @hd = HashDelegator.new
2119
- @hd.instance_variable_set(:@delegate_object,
2120
- { fenced_start_and_end_regex: '^```',
2121
- filename: '/path/to/file' })
2122
- @hd.stubs(:cfile).returns(mock('cfile'))
2123
- end
2289
+ class TestHashDelegatorCommandOrUserSelectedBlock < Minitest::Test
2290
+ def setup
2291
+ @hd = HashDelegator.new
2292
+ @hd.instance_variable_set(:@delegate_object, {})
2293
+ HashDelegator.stubs(:error_handler)
2294
+ @hd.stubs(:wait_for_user_selected_block)
2295
+ end
2124
2296
 
2125
- def test_count_blocks_in_filename
2126
- file_content = ["```ruby\n", "puts 'Hello'\n", "```\n",
2127
- "```python\n", "print('Hello')\n", "```\n"]
2128
- @hd.cfile.stubs(:readlines).with('/path/to/file').returns(file_content)
2297
+ def test_command_selected_block
2298
+ all_blocks = [{ oname: 'block1' }, { oname: 'block2' }]
2299
+ @hd.instance_variable_set(:@delegate_object,
2300
+ { block_name: 'block1' })
2129
2301
 
2130
- count = @hd.count_blocks_in_filename
2302
+ result = @hd.load_cli_or_user_selected_block(all_blocks, [], nil)
2131
2303
 
2132
- assert_equal 2, count
2133
- end
2304
+ assert_equal all_blocks.first.merge(block_name_from_ui: false), result.block
2305
+ assert_nil result.state
2306
+ end
2134
2307
 
2135
- def test_count_blocks_in_filename_with_no_matches
2136
- file_content = ["puts 'Hello'\n", "print('Hello')\n"]
2137
- @hd.cfile.stubs(:readlines).with('/path/to/file').returns(file_content)
2308
+ def test_user_selected_block
2309
+ block_state = SelectedBlockMenuState.new({ oname: 'block2' },
2310
+ :some_state)
2311
+ @hd.stubs(:wait_for_user_selected_block).returns(block_state)
2138
2312
 
2139
- count = @hd.count_blocks_in_filename
2313
+ result = @hd.load_cli_or_user_selected_block([], [], nil)
2140
2314
 
2141
- assert_equal 0, count
2142
- end
2315
+ assert_equal block_state.block.merge(block_name_from_ui: true), result.block
2316
+ assert_equal :some_state, result.state
2143
2317
  end
2318
+ end
2144
2319
 
2145
- class TestHashDelegatorCreateAndWriteFile < Minitest::Test
2146
- def setup
2147
- @hd = HashDelegator.new
2148
- @hd.stubs(:error_handler)
2149
- FileUtils.stubs(:mkdir_p)
2150
- File.stubs(:write)
2151
- File.stubs(:chmod)
2152
- end
2153
-
2154
- def test_create_file_and_write_string_with_permissions
2155
- file_path = '/path/to/file'
2156
- content = 'sample content'
2157
- chmod_value = 0o644
2158
-
2159
- FileUtils.expects(:mkdir_p).with('/path/to').once
2160
- File.expects(:write).with(file_path, content).once
2161
- File.expects(:chmod).with(chmod_value, file_path).once
2320
+ class TestHashDelegatorCountBlockInFilename < Minitest::Test
2321
+ def setup
2322
+ @hd = HashDelegator.new
2323
+ @hd.instance_variable_set(:@delegate_object,
2324
+ { fenced_start_and_end_regex: '^```',
2325
+ filename: '/path/to/file' })
2326
+ @hd.stubs(:cfile).returns(mock('cfile'))
2327
+ end
2162
2328
 
2163
- @hd.create_file_and_write_string_with_permissions(file_path, content,
2164
- chmod_value)
2329
+ def test_count_blocks_in_filename
2330
+ file_content = ["```ruby\n", "puts 'Hello'\n", "```\n",
2331
+ "```python\n", "print('Hello')\n", "```\n"]
2332
+ @hd.cfile.stubs(:readlines).with('/path/to/file',
2333
+ import_paths: nil).returns(file_content)
2165
2334
 
2166
- assert true # Placeholder for actual test assertions
2167
- end
2335
+ count = @hd.count_blocks_in_filename
2168
2336
 
2169
- def test_create_and_write_file_without_chmod
2170
- file_path = '/path/to/file'
2171
- content = 'sample content'
2172
- chmod_value = 0
2337
+ assert_equal 2, count
2338
+ end
2173
2339
 
2174
- FileUtils.expects(:mkdir_p).with('/path/to').once
2175
- File.expects(:write).with(file_path, content).once
2176
- File.expects(:chmod).never
2340
+ def test_count_blocks_in_filename_with_no_matches
2341
+ file_content = ["puts 'Hello'\n", "print('Hello')\n"]
2342
+ @hd.cfile.stubs(:readlines).with('/path/to/file',
2343
+ import_paths: nil).returns(file_content)
2177
2344
 
2178
- @hd.create_file_and_write_string_with_permissions(file_path, content,
2179
- chmod_value)
2345
+ count = @hd.count_blocks_in_filename
2180
2346
 
2181
- assert true # Placeholder for actual test assertions
2182
- end
2347
+ assert_equal 0, count
2183
2348
  end
2349
+ end
2184
2350
 
2185
- class TestHashDelegatorDetermineBlockState < Minitest::Test
2186
- def setup
2187
- @hd = HashDelegator.new
2188
- @hd.stubs(:menu_chrome_formatted_option).returns('Formatted Option')
2189
- end
2351
+ class TestHashDelegatorCreateAndWriteFile < Minitest::Test
2352
+ def setup
2353
+ @hd = HashDelegator.new
2354
+ HashDelegator.stubs(:error_handler)
2355
+ FileUtils.stubs(:mkdir_p)
2356
+ File.stubs(:write)
2357
+ File.stubs(:chmod)
2358
+ end
2190
2359
 
2191
- def test_determine_block_state_exit
2192
- selected_option = { oname: 'Formatted Option' }
2193
- @hd.stubs(:menu_chrome_formatted_option).with(:menu_option_exit_name).returns('Formatted Option')
2360
+ def test_create_file_and_write_string_with_permissions
2361
+ file_path = '/path/to/file'
2362
+ content = 'sample content'
2363
+ chmod_value = 0o644
2194
2364
 
2195
- result = @hd.determine_block_state(selected_option)
2365
+ FileUtils.expects(:mkdir_p).with('/path/to').once
2366
+ File.expects(:write).with(file_path, content).once
2367
+ File.expects(:chmod).with(chmod_value, file_path).once
2196
2368
 
2197
- assert_equal MenuState::EXIT, result.state
2198
- assert_nil result.block
2199
- end
2369
+ HashDelegator.create_file_and_write_string_with_permissions(file_path, content,
2370
+ chmod_value)
2200
2371
 
2201
- def test_determine_block_state_back
2202
- selected_option = { oname: 'Formatted Back Option' }
2203
- @hd.stubs(:menu_chrome_formatted_option).with(:menu_option_back_name).returns('Formatted Back Option')
2204
- result = @hd.determine_block_state(selected_option)
2372
+ assert true # Placeholder for actual test assertions
2373
+ end
2205
2374
 
2206
- assert_equal MenuState::BACK, result.state
2207
- assert_equal selected_option, result.block
2208
- end
2375
+ def test_create_and_write_file_without_chmod
2376
+ file_path = '/path/to/file'
2377
+ content = 'sample content'
2378
+ chmod_value = 0
2209
2379
 
2210
- def test_determine_block_state_continue
2211
- selected_option = { oname: 'Other Option' }
2380
+ FileUtils.expects(:mkdir_p).with('/path/to').once
2381
+ File.expects(:write).with(file_path, content).once
2382
+ File.expects(:chmod).never
2212
2383
 
2213
- result = @hd.determine_block_state(selected_option)
2384
+ HashDelegator.create_file_and_write_string_with_permissions(file_path, content,
2385
+ chmod_value)
2214
2386
 
2215
- assert_equal MenuState::CONTINUE, result.state
2216
- assert_equal selected_option, result.block
2217
- end
2387
+ assert true # Placeholder for actual test assertions
2218
2388
  end
2389
+ end
2219
2390
 
2220
- class TestHashDelegatorDisplayRequiredCode < Minitest::Test
2221
- def setup
2222
- @hd = HashDelegator.new
2223
- @hd.instance_variable_set(:@fout, mock('fout'))
2224
- @hd.instance_variable_set(:@delegate_object, {})
2225
- @hd.stubs(:string_send_color)
2226
- end
2391
+ class TestHashDelegatorDetermineBlockState < Minitest::Test
2392
+ def setup
2393
+ @hd = HashDelegator.new
2394
+ @hd.stubs(:menu_chrome_formatted_option).returns('Formatted Option')
2395
+ end
2227
2396
 
2228
- def test_display_required_code
2229
- required_lines = %w[line1 line2]
2230
- @hd.instance_variable_get(:@delegate_object).stubs(:[]).with(:script_preview_head).returns('Header')
2231
- @hd.instance_variable_get(:@delegate_object).stubs(:[]).with(:script_preview_tail).returns('Footer')
2232
- @hd.instance_variable_get(:@fout).expects(:fout).times(4)
2397
+ def test_determine_block_state_exit
2398
+ selected_option = { oname: 'Formatted Option' }
2399
+ @hd.stubs(:menu_chrome_formatted_option).with(:menu_option_exit_name).returns('Formatted Option')
2233
2400
 
2234
- @hd.display_required_code(required_lines)
2401
+ result = @hd.determine_block_state(selected_option)
2235
2402
 
2236
- # Verifying that fout is called for each line and for header & footer
2237
- assert true # Placeholder for actual test assertions
2238
- end
2403
+ assert_equal MenuState::EXIT, result.state
2404
+ assert_nil result.block
2239
2405
  end
2240
2406
 
2241
- class TestHashDelegatorFetchColor < Minitest::Test
2242
- def setup
2243
- @hd = HashDelegator.new
2244
- @hd.instance_variable_set(:@delegate_object, {})
2245
- end
2407
+ def test_determine_block_state_back
2408
+ selected_option = { oname: 'Formatted Back Option' }
2409
+ @hd.stubs(:menu_chrome_formatted_option).with(:menu_option_back_name).returns('Formatted Back Option')
2410
+ result = @hd.determine_block_state(selected_option)
2246
2411
 
2247
- def test_fetch_color_with_valid_data
2248
- @hd.instance_variable_get(:@delegate_object).stubs(:fetch).with(
2249
- :execution_report_preview_head, ''
2250
- ).returns('Data String')
2251
- @hd.stubs(:string_send_color).with('Data String',
2252
- :execution_report_preview_frame_color).returns('Colored Data String')
2253
-
2254
- result = @hd.fetch_color
2412
+ assert_equal MenuState::BACK, result.state
2413
+ assert_equal selected_option, result.block
2414
+ end
2255
2415
 
2256
- assert_equal 'Colored Data String', result
2257
- end
2416
+ def test_determine_block_state_continue
2417
+ selected_option = { oname: 'Other Option' }
2258
2418
 
2259
- def test_fetch_color_with_missing_data
2260
- @hd.instance_variable_get(:@delegate_object).stubs(:fetch).with(
2261
- :execution_report_preview_head, ''
2262
- ).returns('')
2263
- @hd.stubs(:string_send_color).with('',
2264
- :execution_report_preview_frame_color).returns('Default Colored String')
2419
+ result = @hd.determine_block_state(selected_option)
2265
2420
 
2266
- result = @hd.fetch_color
2421
+ assert_equal MenuState::CONTINUE, result.state
2422
+ assert_equal selected_option, result.block
2423
+ end
2424
+ end
2267
2425
 
2268
- assert_equal 'Default Colored String', result
2269
- end
2426
+ class TestHashDelegatorDisplayRequiredCode < Minitest::Test
2427
+ def setup
2428
+ @hd = HashDelegator.new
2429
+ @hd.instance_variable_set(:@fout, mock('fout'))
2430
+ @hd.instance_variable_set(:@delegate_object, {})
2431
+ @hd.stubs(:string_send_color)
2270
2432
  end
2271
2433
 
2272
- class TestHashDelegatorFormatReferencesSendColor < Minitest::Test
2273
- def setup
2274
- @hd = HashDelegator.new
2275
- @hd.instance_variable_set(:@delegate_object, {})
2276
- end
2434
+ def test_display_required_code
2435
+ required_lines = %w[line1 line2]
2436
+ @hd.instance_variable_get(:@delegate_object).stubs(:[]).with(:script_preview_head).returns('Header')
2437
+ @hd.instance_variable_get(:@delegate_object).stubs(:[]).with(:script_preview_tail).returns('Footer')
2438
+ @hd.instance_variable_get(:@fout).expects(:fout).times(4)
2277
2439
 
2278
- def test_format_references_send_color_with_valid_data
2279
- @hd.instance_variable_get(:@delegate_object).stubs(:fetch).with(
2280
- :output_execution_label_format, ''
2281
- ).returns('Formatted: %{key}')
2282
- @hd.stubs(:string_send_color).returns('Colored String')
2440
+ @hd.display_required_code(required_lines)
2283
2441
 
2284
- result = @hd.format_references_send_color(context: { key: 'value' },
2285
- color_sym: :execution_report_preview_frame_color)
2442
+ # Verifying that fout is called for each line and for header & footer
2443
+ assert true # Placeholder for actual test assertions
2444
+ end
2445
+ end
2286
2446
 
2287
- assert_equal 'Colored String', result
2288
- end
2447
+ class TestHashDelegatorFetchColor < Minitest::Test
2448
+ def setup
2449
+ @hd = HashDelegator.new
2450
+ @hd.instance_variable_set(:@delegate_object, {})
2451
+ end
2289
2452
 
2290
- def test_format_references_send_color_with_missing_format
2291
- @hd.instance_variable_get(:@delegate_object).stubs(:fetch).with(
2292
- :output_execution_label_format, ''
2293
- ).returns('')
2294
- @hd.stubs(:string_send_color).returns('Default Colored String')
2453
+ def test_fetch_color_with_valid_data
2454
+ @hd.instance_variable_get(:@delegate_object).stubs(:fetch).with(
2455
+ :execution_report_preview_head, ''
2456
+ ).returns('Data String')
2457
+ @hd.stubs(:string_send_color).with('Data String',
2458
+ :execution_report_preview_frame_color).returns('Colored Data String')
2295
2459
 
2296
- result = @hd.format_references_send_color(context: { key: 'value' },
2297
- color_sym: :execution_report_preview_frame_color)
2460
+ result = @hd.fetch_color
2298
2461
 
2299
- assert_equal 'Default Colored String', result
2300
- end
2462
+ assert_equal 'Colored Data String', result
2301
2463
  end
2302
2464
 
2303
- class TestHashDelegatorFormatExecutionStreams < Minitest::Test
2304
- def setup
2305
- @hd = HashDelegator.new
2306
- @hd.instance_variable_set(:@run_state, mock('run_state'))
2307
- end
2465
+ def test_fetch_color_with_missing_data
2466
+ @hd.instance_variable_get(:@delegate_object).stubs(:fetch).with(
2467
+ :execution_report_preview_head, ''
2468
+ ).returns('')
2469
+ @hd.stubs(:string_send_color).with('',
2470
+ :execution_report_preview_frame_color).returns('Default Colored String')
2308
2471
 
2309
- def test_format_execution_streams_with_valid_key
2310
- @hd.instance_variable_get(:@run_state).stubs(:files).returns({ stdout: %w[
2311
- output1 output2
2312
- ] })
2472
+ result = @hd.fetch_color
2313
2473
 
2314
- result = @hd.format_execution_streams(:stdout)
2474
+ assert_equal 'Default Colored String', result
2475
+ end
2476
+ end
2315
2477
 
2316
- assert_equal 'output1output2', result
2317
- end
2478
+ class TestHashDelegatorFormatReferencesSendColor < Minitest::Test
2479
+ def setup
2480
+ @hd = HashDelegator.new
2481
+ @hd.instance_variable_set(:@delegate_object, {})
2482
+ end
2318
2483
 
2319
- def test_format_execution_streams_with_empty_key
2320
- @hd.instance_variable_get(:@run_state).stubs(:files).returns({})
2484
+ def test_format_references_send_color_with_valid_data
2485
+ @hd.instance_variable_get(:@delegate_object).stubs(:fetch).with(
2486
+ :output_execution_label_format, ''
2487
+ ).returns('Formatted: %{key}')
2488
+ @hd.stubs(:string_send_color).returns('Colored String')
2321
2489
 
2322
- result = @hd.format_execution_streams(:stderr)
2490
+ result = @hd.format_references_send_color(context: { key: 'value' },
2491
+ color_sym: :execution_report_preview_frame_color)
2323
2492
 
2324
- assert_equal '', result
2325
- end
2493
+ assert_equal 'Colored String', result
2494
+ end
2326
2495
 
2327
- def test_format_execution_streams_with_nil_files
2328
- @hd.instance_variable_get(:@run_state).stubs(:files).returns(nil)
2496
+ def test_format_references_send_color_with_missing_format
2497
+ @hd.instance_variable_get(:@delegate_object).stubs(:fetch).with(
2498
+ :output_execution_label_format, ''
2499
+ ).returns('')
2500
+ @hd.stubs(:string_send_color).returns('Default Colored String')
2329
2501
 
2330
- result = @hd.format_execution_streams(:stdin)
2502
+ result = @hd.format_references_send_color(context: { key: 'value' },
2503
+ color_sym: :execution_report_preview_frame_color)
2331
2504
 
2332
- assert_equal '', result
2333
- end
2505
+ assert_equal 'Default Colored String', result
2334
2506
  end
2507
+ end
2335
2508
 
2336
- class TestHashDelegatorHandleBackLink < Minitest::Test
2337
- def setup
2338
- @hd = HashDelegator.new
2339
- @hd.stubs(:history_state_pop)
2340
- end
2341
-
2342
- def test_pop_link_history_and_trigger_load
2343
- # Verifying that history_state_pop is called
2344
- # @hd.expects(:history_state_pop).once
2509
+ class TestHashDelegatorFormatExecutionStreams < Minitest::Test
2510
+ def setup
2511
+ @hd = HashDelegator.new
2512
+ @hd.instance_variable_set(:@run_state, mock('run_state'))
2513
+ end
2345
2514
 
2346
- result = @hd.pop_link_history_and_trigger_load
2515
+ def test_format_execution_streams_with_valid_key
2516
+ result = HashDelegator.format_execution_streams(:stdout,
2517
+ { stdout: %w[output1 output2] })
2347
2518
 
2348
- # Asserting the result is an instance of LoadFileLinkState
2349
- assert_instance_of LoadFileLinkState, result
2350
- assert_equal LoadFile::Load, result.load_file
2351
- assert_nil result.link_state.block_name
2352
- end
2519
+ assert_equal 'output1output2', result
2353
2520
  end
2354
2521
 
2355
- class TestHashDelegatorHandleBlockState < Minitest::Test
2356
- def setup
2357
- @hd = HashDelegator.new
2358
- @mock_block_state = mock('block_state')
2359
- end
2522
+ def test_format_execution_streams_with_empty_key
2523
+ @hd.instance_variable_get(:@run_state).stubs(:files).returns({})
2360
2524
 
2361
- def test_handle_block_state_with_back
2362
- @mock_block_state.stubs(:state).returns(MenuState::BACK)
2363
- @mock_block_state.stubs(:block).returns({ oname: 'sample_block' })
2525
+ result = HashDelegator.format_execution_streams(:stderr)
2364
2526
 
2365
- @hd.handle_block_state(@mock_block_state)
2527
+ assert_equal '', result
2528
+ end
2366
2529
 
2367
- assert_equal 'sample_block',
2368
- @hd.instance_variable_get(:@delegate_object)[:block_name]
2369
- assert @hd.instance_variable_get(:@menu_user_clicked_back_link)
2370
- end
2530
+ def test_format_execution_streams_with_nil_files
2531
+ @hd.instance_variable_get(:@run_state).stubs(:files).returns(nil)
2371
2532
 
2372
- def test_handle_block_state_with_continue
2373
- @mock_block_state.stubs(:state).returns(MenuState::CONTINUE)
2374
- @mock_block_state.stubs(:block).returns({ oname: 'another_block' })
2533
+ result = HashDelegator.format_execution_streams(:stdin)
2375
2534
 
2376
- @hd.handle_block_state(@mock_block_state)
2535
+ assert_equal '', result
2536
+ end
2537
+ end
2377
2538
 
2378
- assert_equal 'another_block',
2379
- @hd.instance_variable_get(:@delegate_object)[:block_name]
2380
- refute @hd.instance_variable_get(:@menu_user_clicked_back_link)
2381
- end
2539
+ class TestHashDelegatorHandleBackLink < Minitest::Test
2540
+ def setup
2541
+ @hd = HashDelegator.new
2542
+ @hd.stubs(:history_state_pop)
2543
+ end
2382
2544
 
2383
- def test_handle_block_state_with_other
2384
- @mock_block_state.stubs(:state).returns(nil) # MenuState::OTHER
2385
- @mock_block_state.stubs(:block).returns({ oname: 'other_block' })
2545
+ def test_pop_link_history_and_trigger_load
2546
+ # Verifying that history_state_pop is called
2547
+ # @hd.expects(:history_state_pop).once
2386
2548
 
2387
- @hd.handle_block_state(@mock_block_state)
2549
+ result = @hd.pop_link_history_and_trigger_load
2388
2550
 
2389
- assert_nil @hd.instance_variable_get(:@delegate_object)[:block_name]
2390
- assert_nil @hd.instance_variable_get(:@menu_user_clicked_back_link)
2391
- end
2551
+ # Asserting the result is an instance of LoadFileLinkState
2552
+ assert_instance_of LoadFileLinkState, result
2553
+ assert_equal LoadFile::Load, result.load_file
2554
+ assert_nil result.link_state.block_name
2392
2555
  end
2556
+ end
2393
2557
 
2394
- class TestHashDelegatorHandleGenericBlock < Minitest::Test
2395
- def setup
2396
- @hd = HashDelegator.new
2397
- @mock_document = mock('MarkdownDocument')
2398
- @selected_item = mock('FCB')
2399
- end
2558
+ class TestHashDelegatorHandleBlockState < Minitest::Test
2559
+ def setup
2560
+ @hd = HashDelegator.new
2561
+ @mock_block_state = mock('block_state')
2562
+ end
2400
2563
 
2401
- def test_compile_execute_and_trigger_reuse_without_user_approval
2402
- # Mock the delegate object configuration
2403
- @hd.instance_variable_set(:@delegate_object,
2404
- { output_script: false,
2405
- user_must_approve: false })
2564
+ def test_handle_back_or_continue_with_back
2565
+ @mock_block_state.stubs(:state).returns(MenuState::BACK)
2566
+ @mock_block_state.stubs(:block).returns({ oname: 'sample_block' })
2406
2567
 
2407
- # Test the method without user approval
2408
- # Expectations and assertions go here
2409
- end
2568
+ @hd.handle_back_or_continue(@mock_block_state)
2410
2569
 
2411
- def test_compile_execute_and_trigger_reuse_with_user_approval
2412
- # Mock the delegate object configuration
2413
- @hd.instance_variable_set(:@delegate_object,
2414
- { output_script: false,
2415
- user_must_approve: true })
2570
+ assert_equal 'sample_block',
2571
+ @hd.instance_variable_get(:@delegate_object)[:block_name]
2572
+ assert @hd.instance_variable_get(:@menu_user_clicked_back_link)
2573
+ end
2416
2574
 
2417
- # Test the method with user approval
2418
- # Expectations and assertions go here
2419
- end
2575
+ def test_handle_back_or_continue_with_continue
2576
+ @mock_block_state.stubs(:state).returns(MenuState::CONTINUE)
2577
+ @mock_block_state.stubs(:block).returns({ oname: 'another_block' })
2420
2578
 
2421
- def test_compile_execute_and_trigger_reuse_with_output_script
2422
- # Mock the delegate object configuration
2423
- @hd.instance_variable_set(:@delegate_object,
2424
- { output_script: true,
2425
- user_must_approve: false })
2579
+ @hd.handle_back_or_continue(@mock_block_state)
2426
2580
 
2427
- # Test the method with output script option
2428
- # Expectations and assertions go here
2429
- end
2581
+ assert_equal 'another_block',
2582
+ @hd.instance_variable_get(:@delegate_object)[:block_name]
2583
+ refute @hd.instance_variable_get(:@menu_user_clicked_back_link)
2430
2584
  end
2431
2585
 
2432
- # require 'stringio'
2586
+ def test_handle_back_or_continue_with_other
2587
+ @mock_block_state.stubs(:state).returns(nil) # MenuState::OTHER
2588
+ @mock_block_state.stubs(:block).returns({ oname: 'other_block' })
2433
2589
 
2434
- class TestHashDelegatorHandleStream < Minitest::Test
2435
- def setup
2436
- @hd = HashDelegator.new
2437
- @hd.instance_variable_set(:@run_state,
2438
- OpenStruct.new(files: { stdout: [] }))
2439
- @hd.instance_variable_set(:@delegate_object,
2440
- { output_stdout: true })
2441
- end
2590
+ @hd.handle_back_or_continue(@mock_block_state)
2442
2591
 
2443
- def test_handle_stream
2444
- stream = StringIO.new("line 1\nline 2\n")
2445
- file_type = :stdout
2592
+ assert_nil @hd.instance_variable_get(:@delegate_object)[:block_name]
2593
+ assert_nil @hd.instance_variable_get(:@menu_user_clicked_back_link)
2594
+ end
2595
+ end
2446
2596
 
2447
- Thread.new { @hd.handle_stream(stream, file_type) }
2597
+ class TestHashDelegatorHandleGenericBlock < Minitest::Test
2598
+ def setup
2599
+ @hd = HashDelegator.new
2600
+ @mock_document = mock('MarkdownDocument')
2601
+ @selected_item = mock('FCB')
2602
+ end
2448
2603
 
2449
- @hd.wait_for_stream_processing
2604
+ def test_compile_execute_and_trigger_reuse_without_user_approval
2605
+ # Mock the delegate object configuration
2606
+ @hd.instance_variable_set(:@delegate_object,
2607
+ { output_script: false,
2608
+ user_must_approve: false })
2450
2609
 
2451
- assert_equal ['line 1', 'line 2'],
2452
- @hd.instance_variable_get(:@run_state).files[:stdout]
2453
- end
2610
+ # Test the method without user approval
2611
+ # Expectations and assertions go here
2612
+ end
2454
2613
 
2455
- def test_handle_stream_with_io_error
2456
- stream = StringIO.new("line 1\nline 2\n")
2457
- file_type = :stdout
2458
- stream.stubs(:each_line).raises(IOError)
2614
+ def test_compile_execute_and_trigger_reuse_with_user_approval
2615
+ # Mock the delegate object configuration
2616
+ @hd.instance_variable_set(:@delegate_object,
2617
+ { output_script: false,
2618
+ user_must_approve: true })
2459
2619
 
2460
- Thread.new { @hd.handle_stream(stream, file_type) }
2620
+ # Test the method with user approval
2621
+ # Expectations and assertions go here
2622
+ end
2461
2623
 
2462
- @hd.wait_for_stream_processing
2624
+ def test_compile_execute_and_trigger_reuse_with_output_script
2625
+ # Mock the delegate object configuration
2626
+ @hd.instance_variable_set(:@delegate_object,
2627
+ { output_script: true,
2628
+ user_must_approve: false })
2463
2629
 
2464
- assert_equal [],
2465
- @hd.instance_variable_get(:@run_state).files[:stdout]
2466
- end
2630
+ # Test the method with output script option
2631
+ # Expectations and assertions go here
2467
2632
  end
2633
+ end
2468
2634
 
2469
- class TestHashDelegatorIterBlocksFromNestedFiles < Minitest::Test
2470
- def setup
2471
- @hd = HashDelegator.new
2472
- @hd.instance_variable_set(:@delegate_object,
2473
- { filename: 'test.md' })
2474
- @hd.stubs(:check_file_existence).with('test.md').returns(true)
2475
- @hd.stubs(:initial_state).returns({})
2476
- @hd.stubs(:cfile).returns(Minitest::Mock.new)
2477
- @hd.stubs(:update_line_and_block_state)
2478
- end
2635
+ # require 'stringio'
2479
2636
 
2480
- def test_iter_blocks_from_nested_files
2481
- @hd.cfile.expect(:readlines, ['line 1', 'line 2'], ['test.md'])
2482
- selected_messages = ['filtered message']
2637
+ class TestHashDelegatorHandleStream < Minitest::Test
2638
+ def setup
2639
+ @hd = HashDelegator.new
2640
+ @hd.instance_variable_set(:@run_state,
2641
+ OpenStruct.new(files: { stdout: [] }))
2642
+ @hd.instance_variable_set(:@delegate_object,
2643
+ { output_stdout: true })
2644
+ end
2483
2645
 
2484
- result = @hd.iter_blocks_from_nested_files { selected_messages }
2485
- assert_equal ['line 1', 'line 2'], result
2646
+ def test_handle_stream
2647
+ stream = StringIO.new("line 1\nline 2\n")
2648
+ file_type = :stdout
2486
2649
 
2487
- @hd.cfile.verify
2488
- end
2650
+ Thread.new { @hd.handle_stream(stream, file_type) }
2489
2651
 
2490
- def test_iter_blocks_from_nested_files_with_no_file
2491
- @hd.stubs(:check_file_existence).with('test.md').returns(false)
2652
+ @hd.wait_for_stream_processing
2492
2653
 
2493
- assert_nil(@hd.iter_blocks_from_nested_files do
2494
- ['filtered message']
2495
- end)
2496
- end
2654
+ assert_equal ['line 1', 'line 2'],
2655
+ @hd.instance_variable_get(:@run_state).files[:stdout]
2497
2656
  end
2498
2657
 
2499
- class TestHashDelegatorMenuChromeColoredOption < Minitest::Test
2500
- def setup
2501
- @hd = HashDelegator.new
2502
- @hd.instance_variable_set(:@delegate_object, {
2503
- menu_option_back_name: 'Back',
2504
- menu_chrome_color: :red,
2505
- menu_chrome_format: '-- %s --'
2506
- })
2507
- @hd.stubs(:menu_chrome_formatted_option).with(:menu_option_back_name).returns('-- Back --')
2508
- @hd.stubs(:string_send_color).with('-- Back --',
2509
- :menu_chrome_color).returns('-- Back --'.red)
2510
- end
2658
+ def test_handle_stream_with_io_error
2659
+ stream = StringIO.new("line 1\nline 2\n")
2660
+ file_type = :stdout
2661
+ stream.stubs(:each_line).raises(IOError)
2511
2662
 
2512
- def test_menu_chrome_colored_option_with_color
2513
- assert_equal '-- Back --'.red,
2514
- @hd.menu_chrome_colored_option(:menu_option_back_name)
2515
- end
2663
+ Thread.new { @hd.handle_stream(stream, file_type) }
2516
2664
 
2517
- def test_menu_chrome_colored_option_without_color
2518
- @hd.instance_variable_set(:@delegate_object,
2519
- { menu_option_back_name: 'Back' })
2520
- assert_equal '-- Back --',
2521
- @hd.menu_chrome_colored_option(:menu_option_back_name)
2522
- end
2523
- end
2524
-
2525
- class TestHashDelegatorMenuChromeFormattedOptionWithoutFormat < Minitest::Test
2526
- def setup
2527
- @hd = HashDelegator.new
2528
- @hd.instance_variable_set(:@delegate_object, {
2529
- menu_option_back_name: "'Back'",
2530
- menu_chrome_format: '-- %s --'
2531
- })
2532
- @hd.stubs(:safeval).with("'Back'").returns('Back')
2533
- end
2534
-
2535
- def test_menu_chrome_formatted_option_with_format
2536
- assert_equal '-- Back --',
2537
- @hd.menu_chrome_formatted_option(:menu_option_back_name)
2538
- end
2665
+ @hd.wait_for_stream_processing
2539
2666
 
2540
- def test_menu_chrome_formatted_option_without_format
2541
- @hd.instance_variable_set(:@delegate_object,
2542
- { menu_option_back_name: "'Back'" })
2543
- assert_equal 'Back',
2544
- @hd.menu_chrome_formatted_option(:menu_option_back_name)
2545
- end
2667
+ assert_equal [],
2668
+ @hd.instance_variable_get(:@run_state).files[:stdout]
2546
2669
  end
2670
+ end
2547
2671
 
2548
- class TestHashDelegatorStartFencedBlock < Minitest::Test
2549
- def setup
2550
- @hd = HashDelegator.new({
2551
- block_name_wrapper_match: 'WRAPPER_REGEX',
2552
- block_calls_scan: 'CALLS_REGEX'
2553
- })
2554
- end
2672
+ class TestHashDelegatorIterBlocksFromNestedFiles < Minitest::Test
2673
+ def setup
2674
+ @hd = HashDelegator.new
2675
+ @hd.instance_variable_set(:@delegate_object,
2676
+ { filename: 'test.md' })
2677
+ @hd.stubs(:check_file_existence).with('test.md').returns(true)
2678
+ @hd.stubs(:initial_state).returns({})
2679
+ @hd.stubs(:cfile).returns(Minitest::Mock.new)
2680
+ @hd.stubs(:update_line_and_block_state)
2681
+ end
2555
2682
 
2556
- def test_start_fenced_block
2557
- line = '```fenced'
2558
- headings = ['Heading 1']
2559
- regex = /```(?<name>\w+)(?<rest>.*)/
2683
+ def test_iter_blocks_from_nested_files
2684
+ @hd.cfile.expect(:readlines, ['line 1', 'line 2'], ['test.md'], import_paths: nil)
2685
+ selected_messages = ['filtered message']
2560
2686
 
2561
- fcb = @hd.start_fenced_block(line, headings, regex)
2687
+ result = @hd.iter_blocks_from_nested_files { selected_messages }
2688
+ assert_equal ['line 1', 'line 2'], result
2562
2689
 
2563
- assert_instance_of MarkdownExec::FCB, fcb
2564
- assert_equal headings, fcb.headings
2565
- assert_equal 'fenced', fcb.dname
2566
- end
2690
+ @hd.cfile.verify
2567
2691
  end
2568
2692
 
2569
- class TestHashDelegatorStringSendColor < Minitest::Test
2570
- def setup
2571
- @hd = HashDelegator.new
2572
- @hd.instance_variable_set(:@delegate_object,
2573
- { red: 'red', green: 'green' })
2574
- end
2693
+ def test_iter_blocks_from_nested_files_with_no_file
2694
+ @hd.stubs(:check_file_existence).with('test.md').returns(false)
2575
2695
 
2576
- def test_string_send_color
2577
- assert_equal 'Hello'.red, @hd.string_send_color('Hello', :red)
2578
- assert_equal 'World'.green,
2579
- @hd.string_send_color('World', :green)
2580
- assert_equal 'Default'.plain,
2581
- @hd.string_send_color('Default', :blue)
2582
- end
2696
+ assert_nil(@hd.iter_blocks_from_nested_files do
2697
+ ['filtered message']
2698
+ end)
2583
2699
  end
2700
+ end
2584
2701
 
2585
- def test_yield_line_if_selected_with_line
2586
- block_called = false
2587
- @hd.yield_line_if_selected('Test line', [:line]) do |type, content|
2588
- block_called = true
2589
- assert_equal :line, type
2590
- assert_equal 'Test line', content.body[0]
2591
- end
2592
- assert block_called
2702
+ class TestHashDelegatorMenuChromeColoredOption < Minitest::Test
2703
+ def setup
2704
+ @hd = HashDelegator.new
2705
+ @hd.instance_variable_set(:@delegate_object, {
2706
+ menu_option_back_name: 'Back',
2707
+ menu_chrome_color: :red,
2708
+ menu_chrome_format: '-- %s --'
2709
+ })
2710
+ @hd.stubs(:menu_chrome_formatted_option).with(:menu_option_back_name).returns('-- Back --')
2711
+ @hd.stubs(:string_send_color).with('-- Back --',
2712
+ :menu_chrome_color).returns('-- Back --'.red)
2593
2713
  end
2594
2714
 
2595
- def test_yield_line_if_selected_without_line
2596
- block_called = false
2597
- @hd.yield_line_if_selected('Test line', [:other]) do |_|
2598
- block_called = true
2599
- end
2600
- refute block_called
2715
+ def test_menu_chrome_colored_option_with_color
2716
+ assert_equal '-- Back --'.red,
2717
+ @hd.menu_chrome_colored_option(:menu_option_back_name)
2601
2718
  end
2602
2719
 
2603
- def test_yield_line_if_selected_without_block
2604
- result = @hd.yield_line_if_selected('Test line', [:line])
2605
- assert_nil result
2720
+ def test_menu_chrome_colored_option_without_color
2721
+ @hd.instance_variable_set(:@delegate_object,
2722
+ { menu_option_back_name: 'Back' })
2723
+ assert_equal '-- Back --',
2724
+ @hd.menu_chrome_colored_option(:menu_option_back_name)
2606
2725
  end
2607
2726
  end
2608
2727
 
2609
- class TestHashDelegator < Minitest::Test
2728
+ class TestHashDelegatorMenuChromeFormattedOptionWithoutFormat < Minitest::Test
2610
2729
  def setup
2611
2730
  @hd = HashDelegator.new
2612
2731
  @hd.instance_variable_set(:@delegate_object, {
2613
- heading1_match: '^# (?<name>.+)$',
2614
- heading2_match: '^## (?<name>.+)$',
2615
- heading3_match: '^### (?<name>.+)$'
2732
+ menu_option_back_name: "'Back'",
2733
+ menu_chrome_format: '-- %s --'
2616
2734
  })
2735
+ HashDelegator.stubs(:safeval).with("'Back'").returns('Back')
2617
2736
  end
2618
2737
 
2619
- def test_update_document_headings
2620
- assert_equal(['Heading 1'],
2621
- @hd.update_document_headings('# Heading 1', []))
2622
- assert_equal(['Heading 1', 'Heading 2'],
2623
- @hd.update_document_headings('## Heading 2',
2624
- ['Heading 1']))
2625
- assert_equal(['Heading 1', 'Heading 2', 'Heading 3'],
2626
- @hd.update_document_headings('### Heading 3',
2627
- ['Heading 1', 'Heading 2']))
2628
- assert_equal([], @hd.update_document_headings('Regular text', []))
2738
+ def test_menu_chrome_formatted_option_with_format
2739
+ assert_equal '-- Back --',
2740
+ @hd.menu_chrome_formatted_option(:menu_option_back_name)
2741
+ end
2742
+
2743
+ def test_menu_chrome_formatted_option_without_format
2744
+ @hd.instance_variable_set(:@delegate_object,
2745
+ { menu_option_back_name: "'Back'" })
2746
+ assert_equal 'Back',
2747
+ @hd.menu_chrome_formatted_option(:menu_option_back_name)
2629
2748
  end
2630
2749
  end
2631
2750
 
2632
- class TestHashDelegatorUpdateMenuAttribYieldSelectedWithBody < Minitest::Test
2751
+ class TestHashDelegatorStartFencedBlock < Minitest::Test
2633
2752
  def setup
2634
- @hd = HashDelegator.new
2635
- @fcb = mock('Fcb')
2636
- @fcb.stubs(:body).returns(true)
2637
- @hd.stubs(:initialize_fcb_names)
2638
- @hd.stubs(:default_block_title_from_body)
2639
- @hd.stubs(:yield_to_block_if_applicable)
2753
+ @hd = HashDelegator.new({
2754
+ block_name_wrapper_match: 'WRAPPER_REGEX',
2755
+ block_calls_scan: 'CALLS_REGEX'
2756
+ })
2640
2757
  end
2641
2758
 
2642
- def test_update_menu_attrib_yield_selected_with_body
2643
- @hd.expects(:initialize_fcb_names).with(@fcb)
2644
- @hd.expects(:default_block_title_from_body).with(@fcb)
2645
- @hd.expects(:yield_to_block_if_applicable).with(@fcb, [:some_message])
2759
+ def test_start_fenced_block
2760
+ line = '```fenced'
2761
+ headings = ['Heading 1']
2762
+ regex = /```(?<name>\w+)(?<rest>.*)/
2646
2763
 
2647
- @hd.update_menu_attrib_yield_selected(@fcb, [:some_message])
2648
- end
2764
+ fcb = @hd.start_fenced_block(line, headings, regex)
2649
2765
 
2650
- def test_update_menu_attrib_yield_selected_without_body
2651
- @fcb.stubs(:body).returns(nil)
2652
- @hd.expects(:initialize_fcb_names).with(@fcb)
2653
- @hd.update_menu_attrib_yield_selected(@fcb, [:some_message])
2766
+ assert_instance_of MarkdownExec::FCB, fcb
2767
+ assert_equal headings, fcb.headings
2768
+ assert_equal 'fenced', fcb.dname
2654
2769
  end
2655
2770
  end
2656
2771
 
2657
- class TestHashDelegatorWaitForUserSelectedBlock < Minitest::Test
2772
+ class TestHashDelegatorStringSendColor < Minitest::Test
2658
2773
  def setup
2659
2774
  @hd = HashDelegator.new
2660
- @hd.stubs(:error_handler)
2775
+ @hd.instance_variable_set(:@delegate_object,
2776
+ { red: 'red', green: 'green' })
2661
2777
  end
2662
2778
 
2663
- def test_wait_for_user_selected_block_with_back_state
2664
- mock_block_state = Struct.new(:state, :block).new(MenuState::BACK,
2665
- { oname: 'back_block' })
2666
- @hd.stubs(:wait_for_user_selection).returns(mock_block_state)
2779
+ def test_string_send_color
2780
+ assert_equal 'Hello'.red, @hd.string_send_color('Hello', :red)
2781
+ assert_equal 'World'.green,
2782
+ @hd.string_send_color('World', :green)
2783
+ assert_equal 'Default'.plain,
2784
+ @hd.string_send_color('Default', :blue)
2785
+ end
2786
+ end
2667
2787
 
2668
- result = @hd.wait_for_user_selected_block([], ['Block 1', 'Block 2'],
2669
- nil)
2788
+ def test_yield_line_if_selected_with_line
2789
+ block_called = false
2790
+ HashDelegator.yield_line_if_selected('Test line', [:line]) do |type, content|
2791
+ block_called = true
2792
+ assert_equal :line, type
2793
+ assert_equal 'Test line', content.body[0]
2794
+ end
2795
+ assert block_called
2796
+ end
2670
2797
 
2671
- assert_equal 'back_block',
2672
- @hd.instance_variable_get(:@delegate_object)[:block_name]
2673
- assert @hd.instance_variable_get(:@menu_user_clicked_back_link)
2674
- assert_equal mock_block_state, result
2798
+ def test_yield_line_if_selected_without_line
2799
+ block_called = false
2800
+ HashDelegator.yield_line_if_selected('Test line', [:other]) do |_|
2801
+ block_called = true
2675
2802
  end
2803
+ refute block_called
2804
+ end
2676
2805
 
2677
- def test_wait_for_user_selected_block_with_continue_state
2678
- mock_block_state = Struct.new(:state, :block).new(
2679
- MenuState::CONTINUE, { oname: 'continue_block' }
2680
- )
2681
- @hd.stubs(:wait_for_user_selection).returns(mock_block_state)
2806
+ def test_yield_line_if_selected_without_block
2807
+ result = HashDelegator.yield_line_if_selected('Test line', [:line])
2808
+ assert_nil result
2809
+ end
2810
+ end
2682
2811
 
2683
- result = @hd.wait_for_user_selected_block([], ['Block 1', 'Block 2'],
2684
- nil)
2812
+ class TestHashDelegatorUpdateMenuAttribYieldSelectedWithBody < Minitest::Test
2813
+ def setup
2814
+ @hd = HashDelegator.new
2815
+ @fcb = mock('Fcb')
2816
+ @fcb.stubs(:body).returns(true)
2817
+ HashDelegator.stubs(:initialize_fcb_names)
2818
+ HashDelegator.stubs(:default_block_title_from_body)
2819
+ Filter.stubs(:yield_to_block_if_applicable)
2820
+ end
2685
2821
 
2686
- assert_equal 'continue_block',
2687
- @hd.instance_variable_get(:@delegate_object)[:block_name]
2688
- refute @hd.instance_variable_get(:@menu_user_clicked_back_link)
2689
- assert_equal mock_block_state, result
2690
- end
2822
+ def test_update_menu_attrib_yield_selected_with_body
2823
+ HashDelegator.expects(:initialize_fcb_names).with(@fcb)
2824
+ HashDelegator.expects(:default_block_title_from_body).with(@fcb)
2825
+ Filter.expects(:yield_to_block_if_applicable).with(@fcb, [:some_message], {})
2826
+
2827
+ HashDelegator.update_menu_attrib_yield_selected(@fcb, [:some_message])
2691
2828
  end
2692
2829
 
2693
- class TestHashDelegatorYieldToBlock < Minitest::Test
2694
- def setup
2695
- @hd = HashDelegator.new
2696
- @fcb = mock('Fcb')
2697
- MarkdownExec::Filter.stubs(:fcb_select?).returns(true)
2698
- end
2830
+ def test_update_menu_attrib_yield_selected_without_body
2831
+ @fcb.stubs(:body).returns(nil)
2832
+ HashDelegator.expects(:initialize_fcb_names).with(@fcb)
2833
+ HashDelegator.update_menu_attrib_yield_selected(@fcb, [:some_message])
2834
+ end
2835
+ end
2699
2836
 
2700
- def test_yield_to_block_if_applicable_with_correct_conditions
2701
- block_called = false
2702
- @hd.yield_to_block_if_applicable(@fcb, [:blocks]) do |type, fcb|
2703
- block_called = true
2704
- assert_equal :blocks, type
2705
- assert_equal @fcb, fcb
2706
- end
2707
- assert block_called
2708
- end
2837
+ class TestHashDelegatorWaitForUserSelectedBlock < Minitest::Test
2838
+ def setup
2839
+ @hd = HashDelegator.new
2840
+ HashDelegator.stubs(:error_handler)
2841
+ end
2709
2842
 
2710
- def test_yield_to_block_if_applicable_without_block
2711
- result = @hd.yield_to_block_if_applicable(@fcb, [:blocks])
2712
- assert_nil result
2843
+ def test_wait_for_user_selected_block_with_back_state
2844
+ mock_block_state = Struct.new(:state, :block).new(MenuState::BACK,
2845
+ { oname: 'back_block' })
2846
+ @hd.stubs(:wait_for_user_selection).returns(mock_block_state)
2847
+
2848
+ result = @hd.wait_for_user_selected_block([], ['Block 1', 'Block 2'],
2849
+ nil)
2850
+
2851
+ assert_equal 'back_block',
2852
+ @hd.instance_variable_get(:@delegate_object)[:block_name]
2853
+ assert @hd.instance_variable_get(:@menu_user_clicked_back_link)
2854
+ assert_equal mock_block_state, result
2855
+ end
2856
+
2857
+ def test_wait_for_user_selected_block_with_continue_state
2858
+ mock_block_state = Struct.new(:state, :block).new(
2859
+ MenuState::CONTINUE, { oname: 'continue_block' }
2860
+ )
2861
+ @hd.stubs(:wait_for_user_selection).returns(mock_block_state)
2862
+
2863
+ result = @hd.wait_for_user_selected_block([], ['Block 1', 'Block 2'],
2864
+ nil)
2865
+
2866
+ assert_equal 'continue_block',
2867
+ @hd.instance_variable_get(:@delegate_object)[:block_name]
2868
+ refute @hd.instance_variable_get(:@menu_user_clicked_back_link)
2869
+ assert_equal mock_block_state, result
2870
+ end
2871
+ end
2872
+
2873
+ class TestHashDelegatorYieldToBlock < Minitest::Test
2874
+ def setup
2875
+ @hd = HashDelegator.new
2876
+ @fcb = mock('Fcb')
2877
+ MarkdownExec::Filter.stubs(:fcb_select?).returns(true)
2878
+ end
2879
+
2880
+ def test_yield_to_block_if_applicable_with_correct_conditions
2881
+ block_called = false
2882
+ Filter.yield_to_block_if_applicable(@fcb, [:blocks]) do |type, fcb|
2883
+ block_called = true
2884
+ assert_equal :blocks, type
2885
+ assert_equal @fcb, fcb
2713
2886
  end
2887
+ assert block_called
2888
+ end
2714
2889
 
2715
- def test_yield_to_block_if_applicable_with_incorrect_conditions
2716
- block_called = false
2717
- MarkdownExec::Filter.stubs(:fcb_select?).returns(false)
2718
- @hd.yield_to_block_if_applicable(@fcb, [:non_blocks]) do |_|
2719
- block_called = true
2720
- end
2721
- refute block_called
2890
+ def test_yield_to_block_if_applicable_without_block
2891
+ result = Filter.yield_to_block_if_applicable(@fcb, [:blocks])
2892
+ assert_nil result
2893
+ end
2894
+
2895
+ def test_yield_to_block_if_applicable_with_incorrect_conditions
2896
+ block_called = false
2897
+ MarkdownExec::Filter.stubs(:fcb_select?).returns(false)
2898
+ Filter.yield_to_block_if_applicable(@fcb, [:non_blocks]) do |_|
2899
+ block_called = true
2722
2900
  end
2901
+ refute block_called
2723
2902
  end
2724
- end # module MarkdownExec
2725
- end
2903
+ end
2904
+ end # module MarkdownExec