markdown_exec 1.8.6 → 1.8.8

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