markdown_exec 1.8.1 → 1.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -24,6 +24,7 @@ require_relative 'fcb'
24
24
  require_relative 'filter'
25
25
  require_relative 'fout'
26
26
  require_relative 'hash'
27
+ require_relative 'link_history'
27
28
  require_relative 'mdoc'
28
29
  require_relative 'string_util'
29
30
 
@@ -115,6 +116,7 @@ module MarkdownExec
115
116
  @run_state = OpenStruct.new(
116
117
  link_history: []
117
118
  )
119
+ @link_history = LinkHistory.new
118
120
  @fout = FOut.new(@delegate_object) ### slice only relevant keys
119
121
 
120
122
  @process_mutex = Mutex.new
@@ -145,10 +147,6 @@ module MarkdownExec
145
147
 
146
148
  private
147
149
 
148
- def should_add_back_option?
149
- @delegate_object[:menu_with_back] && history_env_state_exist?
150
- end
151
-
152
150
  def add_back_option(menu_blocks)
153
151
  append_chrome_block(menu_blocks, MenuState::BACK)
154
152
  end
@@ -171,9 +169,7 @@ module MarkdownExec
171
169
  def append_chrome_block(menu_blocks, type)
172
170
  case type
173
171
  when MenuState::BACK
174
- state = history_state_partition
175
- @hs_curr = state[:unit]
176
- @hs_rest = state[:rest]
172
+ history_state_partition
177
173
  option_name = @delegate_object[:menu_option_back_name]
178
174
  insert_at_top = @delegate_object[:menu_back_at_top]
179
175
  when MenuState::EXIT
@@ -185,13 +181,9 @@ module MarkdownExec
185
181
  safeval(option_name))
186
182
  chrome_block = FCB.new(
187
183
  chrome: true,
188
-
189
- # dname: formatted_name.send(@delegate_object[:menu_link_color].to_sym),
190
184
  dname: HashDelegator.new(@delegate_object).string_send_color(
191
185
  formatted_name, :menu_link_color
192
186
  ),
193
- #dname: @delegate_object.string_send_color(formatted_name, :menu_link_color),
194
-
195
187
  oname: formatted_name
196
188
  )
197
189
 
@@ -216,42 +208,16 @@ module MarkdownExec
216
208
 
217
209
  # private
218
210
 
219
- def divider_formatting_present?(position)
220
- divider_key = position == :initial ? :menu_initial_divider : :menu_final_divider
221
- @delegate_object[:menu_divider_format].present? && @delegate_object[divider_key].present?
222
- end
223
-
224
- def create_divider(position)
225
- divider_key = position == :initial ? :menu_initial_divider : :menu_final_divider
226
- oname = format(@delegate_object[:menu_divider_format],
227
- safeval(@delegate_object[divider_key]))
228
-
229
- FCB.new(
230
- chrome: true,
231
- disabled: '',
232
- dname: string_send_color(oname, :menu_divider_color),
233
- oname: oname
234
- )
235
- end
236
-
237
- # Execute a code block after approval and provide user interaction options.
238
- #
239
- # This method displays required code blocks, asks for user approval, and
240
- # executes the code block if approved. It also allows users to copy the
241
- # code to the clipboard or save it to a file.
242
- #
243
- # @param opts [Hash] Options hash containing configuration settings.
244
- # @param mdoc [YourMDocClass] An instance of the MDoc class.
211
+ # Applies shell color options to the given string if applicable.
245
212
  #
246
- def approve_and_execute_block(selected, mdoc)
247
- if selected.fetch(:shell, '') == BlockType::LINK
248
- handle_link_block(selected.fetch(:body, ''), mdoc, selected)
249
- elsif @menu_user_clicked_back_link
250
- handle_back_link
251
- elsif selected[:shell] == BlockType::OPTS
252
- handle_opts_block(selected, @menu_base_options)
213
+ # @param name [String] The name to potentially colorize.
214
+ # @param shell_color_option [Symbol, nil] The shell color option to apply.
215
+ # @return [String] The colorized or original name string.
216
+ def apply_shell_color_option(name, shell_color_option)
217
+ if shell_color_option && @delegate_object[shell_color_option].present?
218
+ string_send_color(name, shell_color_option)
253
219
  else
254
- handle_generic_block(mdoc, selected)
220
+ name
255
221
  end
256
222
  end
257
223
 
@@ -286,22 +252,6 @@ module MarkdownExec
286
252
 
287
253
  # private
288
254
 
289
- def process_block_based_on_type(blocks, btype, fcb)
290
- case btype
291
- when :blocks
292
- blocks.push(get_block_summary(fcb))
293
- when :filter
294
- %i[blocks line]
295
- when :line
296
- unless @delegate_object[:no_chrome]
297
- create_and_add_chrome_blocks(blocks,
298
- fcb)
299
- end
300
- end
301
- end
302
-
303
- # private
304
-
305
255
  def cfile
306
256
  @cfile ||= CachedNestedFileReader.new(
307
257
  import_pattern: @delegate_object.fetch(:import_pattern) #, "^ *@import +(?<name>.+?) *$")
@@ -322,23 +272,13 @@ module MarkdownExec
322
272
  true
323
273
  end
324
274
 
325
- def runtime_exception(exception_sym, name, items)
326
- if @delegate_object[exception_sym] != 0
327
- data = { name: name, detail: items.join(', ') }
328
- warn(
329
- format(
330
- @delegate_object.fetch(:exception_format_name, "\n%{name}"),
331
- data
332
- ).send(@delegate_object.fetch(:exception_color_name, :red)) +
333
- format(
334
- @delegate_object.fetch(:exception_format_detail, " - %{detail}\n"),
335
- data
336
- ).send(@delegate_object.fetch(:exception_color_detail, :yellow))
337
- )
338
- end
339
- return unless (@delegate_object[exception_sym]).positive?
275
+ def code_join(*bodies)
276
+ bc = bodies&.compact
277
+ bc.count.positive? ? bc.join("\n") : nil
278
+ end
340
279
 
341
- exit @delegate_object[exception_sym]
280
+ def code_merge(*bodies)
281
+ merge_lists(*bodies)
342
282
  end
343
283
 
344
284
  # Collects required code lines based on the selected block and the delegate object's configuration.
@@ -347,15 +287,20 @@ module MarkdownExec
347
287
  # @param mdoc [YourMDocClass] An instance of the MDoc class.
348
288
  # @param selected [Hash] The selected block.
349
289
  # @return [Array<String>] Required code blocks as an array of lines.
350
- def collect_required_code_lines(mdoc, selected)
351
- set_environment_variables(selected) if selected[:shell] == BlockType::VARS
290
+ def collect_required_code_lines(mdoc, selected, link_state = LinkState.new, block_source:)
291
+ set_environment_variables_for_block(selected) if selected[:shell] == BlockType::VARS
352
292
 
353
293
  required = mdoc.collect_recursively_required_code(
354
294
  @delegate_object[:block_name],
355
295
  label_format_above: @delegate_object[:shell_code_label_format_above],
356
- label_format_below: @delegate_object[:shell_code_label_format_below]
296
+ label_format_below: @delegate_object[:shell_code_label_format_below],
297
+ block_source: block_source
357
298
  )
299
+ required[:unmet_dependencies] =
300
+ (required[:unmet_dependencies] || []) - (link_state&.inherited_block_names || [])
358
301
  if required[:unmet_dependencies].present?
302
+ ### filter against link_state.inherited_block_names
303
+
359
304
  warn format_and_highlight_dependencies(required[:dependencies],
360
305
  highlight: required[:unmet_dependencies])
361
306
  runtime_exception(:runtime_exception_error_level,
@@ -364,27 +309,14 @@ module MarkdownExec
364
309
  warn format_and_highlight_dependencies(required[:dependencies],
365
310
  highlight: [@delegate_object[:block_name]])
366
311
  end
367
- read_required_blocks_from_temp_file + required[:code]
368
- end
369
-
370
- # private
371
-
372
- def set_environment_variables(selected)
373
- YAML.load(selected[:body].join("\n")).each do |key, value|
374
- ENV[key] = value.to_s
375
- next unless @delegate_object[:menu_vars_set_format].present?
376
312
 
377
- formatted_string = format(@delegate_object[:menu_vars_set_format],
378
- { key: key, value: value })
379
- print string_send_color(formatted_string, :menu_vars_set_color)
380
- end
313
+ code_merge link_state&.inherited_lines, required[:code]
381
314
  end
382
315
 
383
316
  def command_execute(command, args: [])
384
317
  @run_state.files = Hash.new([])
385
318
  @run_state.options = @delegate_object
386
319
  @run_state.started_at = Time.now.utc
387
- # rbp
388
320
 
389
321
  Open3.popen3(@delegate_object[:shell],
390
322
  '-c', command,
@@ -442,6 +374,26 @@ module MarkdownExec
442
374
  error_handler('command_or_user_selected_block')
443
375
  end
444
376
 
377
+ # This method is responsible for handling the execution of generic blocks in a markdown document.
378
+ # It collects the required code lines from the document and, depending on the configuration,
379
+ # may display the code for user approval before execution. It then executes the approved block.
380
+ #
381
+ # @param mdoc [Object] The markdown document object containing code blocks.
382
+ # @param selected [Hash] The selected item from the menu to be executed.
383
+ # @return [LoadFileLinkState] An object indicating whether to load the next block or reuse the current one.
384
+ def compile_execute_bash_and_special_blocks_and_trigger_reuse(mdoc, selected,
385
+ link_state = nil, block_source:)
386
+ required_lines = collect_required_code_lines(mdoc, selected, link_state,
387
+ block_source: block_source)
388
+ output_or_approval = @delegate_object[:output_script] || @delegate_object[:user_must_approve]
389
+ display_required_code(required_lines) if output_or_approval
390
+ allow_execution = @delegate_object[:user_must_approve] ? prompt_for_user_approval(required_lines) : true
391
+ execute_required_lines(required_lines) if allow_execution
392
+
393
+ link_state.block_name = nil
394
+ LoadFileLinkState.new(LoadFile::Reuse, link_state)
395
+ end
396
+
445
397
  def copy_to_clipboard(required_lines)
446
398
  text = required_lines.flatten.join($INPUT_RECORD_SEPARATOR)
447
399
  Clipboard.copy(text)
@@ -520,13 +472,13 @@ module MarkdownExec
520
472
  # @param file_path [String] The path where the file will be created.
521
473
  # @param content [String] The content to write into the file.
522
474
  # @param chmod_value [Integer] The file permission value to set; skips if zero.
523
- def create_and_write_file_with_permissions(file_path, content,
524
- chmod_value)
475
+ def create_file_and_write_string_with_permissions(file_path, content,
476
+ chmod_value)
525
477
  create_directory_for_file(file_path)
526
- write_file_content(file_path, content)
478
+ File.write(file_path, content)
527
479
  set_file_permissions(file_path, chmod_value) unless chmod_value.zero?
528
480
  rescue StandardError
529
- error_handler('create_and_write_file_with_permissions')
481
+ error_handler('create_file_and_write_string_with_permissions')
530
482
  end
531
483
 
532
484
  # private
@@ -535,21 +487,17 @@ module MarkdownExec
535
487
  FileUtils.mkdir_p(File.dirname(file_path))
536
488
  end
537
489
 
538
- def write_file_content(file_path, content)
539
- File.write(file_path, content)
540
- end
541
-
542
- def set_file_permissions(file_path, chmod_value)
543
- File.chmod(chmod_value, file_path)
544
- end
490
+ def create_divider(position)
491
+ divider_key = position == :initial ? :menu_initial_divider : :menu_final_divider
492
+ oname = format(@delegate_object[:menu_divider_format],
493
+ safeval(@delegate_object[divider_key]))
545
494
 
546
- # Creates a temporary file, writes the provided code blocks into it,
547
- # and sets an environment variable with the file path.
548
- # @param code_blocks [String] Code blocks to write into the file.
549
- def create_temp_file_with_code(code_blocks)
550
- temp_file_path = create_temp_file
551
- write_to_file(temp_file_path, code_blocks)
552
- set_environment_variable(temp_file_path)
495
+ FCB.new(
496
+ chrome: true,
497
+ disabled: '',
498
+ dname: string_send_color(oname, :menu_divider_color),
499
+ oname: oname
500
+ )
553
501
  end
554
502
 
555
503
  # private
@@ -558,14 +506,6 @@ module MarkdownExec
558
506
  Dir::Tmpname.create(self.class.to_s) { |path| path }
559
507
  end
560
508
 
561
- def write_to_file(path, content)
562
- File.write(path, content)
563
- end
564
-
565
- def set_environment_variable(path)
566
- ENV['MDE_LINK_REQUIRED_FILE'] = path
567
- end
568
-
569
509
  # Updates the title of an FCB object from its body content if the title is nil or empty.
570
510
  def default_block_title_from_body(fcb)
571
511
  return unless fcb.title.nil? || fcb.title.empty?
@@ -584,27 +524,14 @@ module MarkdownExec
584
524
  # Deletes a temporary file specified by an environment variable.
585
525
  # Checks if the file exists before attempting to delete it and clears the environment variable afterward.
586
526
  # Any errors encountered during deletion are handled gracefully.
587
- def delete_required_temp_file
588
- temp_blocks_file_path = fetch_temp_blocks_file_path
589
-
527
+ def delete_required_temp_file(temp_blocks_file_path)
590
528
  return if temp_blocks_file_path.nil? || temp_blocks_file_path.empty?
591
529
 
592
530
  safely_remove_file(temp_blocks_file_path)
593
- clear_required_file
594
531
  rescue StandardError
595
532
  error_handler('delete_required_temp_file')
596
533
  end
597
534
 
598
- # private
599
-
600
- def fetch_temp_blocks_file_path
601
- ENV.fetch('MDE_LINK_REQUIRED_FILE', nil)
602
- end
603
-
604
- def safely_remove_file(path)
605
- FileUtils.rm_f(path)
606
- end
607
-
608
535
  # Determines the state of a selected block in the menu based on the selected option.
609
536
  # It categorizes the selected option into either EXIT, BACK, or CONTINUE state.
610
537
  #
@@ -636,12 +563,9 @@ module MarkdownExec
636
563
  :script_preview_frame_color)
637
564
  end
638
565
 
639
- # private
640
-
641
- def output_color_formatted(data_sym, color_sym)
642
- formatted_string = string_send_color(@delegate_object[data_sym],
643
- color_sym)
644
- @fout.fout formatted_string
566
+ def divider_formatting_present?(position)
567
+ divider_key = position == :initial ? :menu_initial_divider : :menu_final_divider
568
+ @delegate_object[:menu_divider_format].present? && @delegate_object[divider_key].present?
645
569
  end
646
570
 
647
571
  def error_handler(name = '', opts = {})
@@ -659,36 +583,38 @@ module MarkdownExec
659
583
  #
660
584
  # @param required_lines [Array<String>] The lines of code to be executed.
661
585
  # @param selected [FCB] The selected functional code block object.
662
- def execute_approved_block(required_lines = [], _selected = FCB.new)
586
+ def execute_required_lines(required_lines = [])
663
587
  # set_script_block_name(selected)
664
- write_command_file_if_needed(required_lines)
588
+ save_executed_script_if_specified(required_lines)
665
589
  format_and_execute_command(required_lines)
666
590
  post_execution_process
667
591
  end
668
592
 
669
- # private
670
-
671
- def set_script_block_name(selected)
672
- @run_state.script_block_name = selected[:oname]
673
- end
593
+ # Execute a code block after approval and provide user interaction options.
594
+ #
595
+ # This method displays required code blocks, asks for user approval, and
596
+ # executes the code block if approved. It also allows users to copy the
597
+ # code to the clipboard or save it to a file.
598
+ #
599
+ # @param opts [Hash] Options hash containing configuration settings.
600
+ # @param mdoc [YourMDocClass] An instance of the MDoc class.
601
+ #
602
+ def execute_bash_and_special_blocks(selected, mdoc, link_state = LinkState.new,
603
+ block_source:)
604
+ if selected.fetch(:shell, '') == BlockType::LINK
605
+ push_link_history_and_trigger_load(selected.fetch(:body, ''), mdoc, selected,
606
+ link_state)
674
607
 
675
- def write_command_file_if_needed(lines)
676
- write_command_file(lines) if @delegate_object[:save_executed_script]
677
- end
608
+ elsif @menu_user_clicked_back_link
609
+ pop_link_history_and_trigger_load
678
610
 
679
- def format_and_execute_command(lines)
680
- formatted_command = lines.flatten.join("\n")
681
- @fout.fout fetch_color(data_sym: :script_execution_head,
682
- color_sym: :script_execution_frame_color)
683
- command_execute(formatted_command, args: @pass_args)
684
- @fout.fout fetch_color(data_sym: :script_execution_tail,
685
- color_sym: :script_execution_frame_color)
686
- end
611
+ elsif selected[:shell] == BlockType::OPTS
612
+ update_options_and_trigger_reuse(selected, @menu_base_options, link_state)
687
613
 
688
- def post_execution_process
689
- initialize_and_save_execution_output
690
- output_execution_summary
691
- output_execution_result
614
+ else
615
+ compile_execute_bash_and_special_blocks_and_trigger_reuse(mdoc, selected, link_state,
616
+ block_source: block_source)
617
+ end
692
618
  end
693
619
 
694
620
  # Retrieves a specific data symbol from the delegate object, converts it to a string,
@@ -705,6 +631,26 @@ module MarkdownExec
705
631
  string_send_color(data_string, color_sym)
706
632
  end
707
633
 
634
+ # DebugHelper.d ["HDmm method_name: #{method_name}", "#{first_n_caller_items 1}"]
635
+ def first_n_caller_items(n)
636
+ call_stack = caller
637
+ base_path = File.realpath('.')
638
+
639
+ # Modify the call stack to remove the base path and keep only the first n items
640
+ call_stack.take(n + 1)[1..].map do |line|
641
+ " . #{line.sub(/^#{Regexp.escape(base_path)}\//, '')}"
642
+ end.join("\n")
643
+ end
644
+
645
+ def format_and_execute_command(lines)
646
+ formatted_command = lines.flatten.join("\n")
647
+ @fout.fout fetch_color(data_sym: :script_execution_head,
648
+ color_sym: :script_execution_frame_color)
649
+ command_execute(formatted_command, args: @pass_args)
650
+ @fout.fout fetch_color(data_sym: :script_execution_tail,
651
+ color_sym: :script_execution_frame_color)
652
+ end
653
+
708
654
  # Formats a string based on a given context and applies color styling to it.
709
655
  # It retrieves format and color information from the delegate object and processes accordingly.
710
656
  #
@@ -756,124 +702,21 @@ module MarkdownExec
756
702
  fcb
757
703
  end
758
704
 
759
- # private
760
-
761
- # Applies shell color options to the given string if applicable.
762
- #
763
- # @param name [String] The name to potentially colorize.
764
- # @param shell_color_option [Symbol, nil] The shell color option to apply.
765
- # @return [String] The colorized or original name string.
766
- def apply_shell_color_option(name, shell_color_option)
767
- if shell_color_option && @delegate_object[shell_color_option].present?
768
- string_send_color(name, shell_color_option)
769
- else
770
- name
771
- end
772
- end
773
-
774
- # This method handles the back-link operation in the Markdown execution context.
775
- # It updates the history state and prepares to load the next block.
776
- #
777
- # @return [LoadFileNextBlock] An object indicating the action to load the next block.
778
- def handle_back_link
779
- history_state_pop
780
- LoadFileNextBlock.new(LoadFile::Load, '')
781
- end
782
-
783
- # private
784
705
  # Updates the delegate object's state based on the provided block state.
785
706
  # It sets the block name and determines if the user clicked the back link in the menu.
786
707
  #
787
708
  # @param block_state [Object] An object representing the state of a block in the menu.
788
709
  def handle_block_state(block_state)
710
+ return if block_state.nil?
789
711
  unless [MenuState::BACK,
790
712
  MenuState::CONTINUE].include?(block_state.state)
791
713
  return
792
714
  end
793
715
 
794
716
  @delegate_object[:block_name] = block_state.block[:oname]
795
- # rbp
796
717
  @menu_user_clicked_back_link = block_state.state == MenuState::BACK
797
718
  end
798
719
 
799
- # This method is responsible for handling the execution of generic blocks in a markdown document.
800
- # It collects the required code lines from the document and, depending on the configuration,
801
- # may display the code for user approval before execution. It then executes the approved block.
802
- #
803
- # @param mdoc [Object] The markdown document object containing code blocks.
804
- # @param selected [Hash] The selected item from the menu to be executed.
805
- # @return [LoadFileNextBlock] An object indicating whether to load the next block or reuse the current one.
806
- def handle_generic_block(mdoc, selected)
807
- # rbp
808
- required_lines = collect_required_code_lines(mdoc, selected)
809
- output_or_approval = @delegate_object[:output_script] || @delegate_object[:user_must_approve]
810
- display_required_code(required_lines) if output_or_approval
811
- allow_execution = @delegate_object[:user_must_approve] ? prompt_for_user_approval(required_lines) : true
812
- execute_approved_block(required_lines, selected) if allow_execution
813
-
814
- LoadFileNextBlock.new(LoadFile::Reuse, '')
815
- end
816
-
817
- # Handles the processing of a link block in Markdown Execution.
818
- # It loads YAML data from the body content, pushes the state to history,
819
- # sets environment variables, and decides on the next block to load.
820
- #
821
- # @param body [Array<String>] The body content as an array of strings.
822
- # @param mdoc [Object] Markdown document object.
823
- # @param selected [Boolean] Selected state.
824
- # @return [LoadFileNextBlock] Object indicating the next action for file loading.
825
- def handle_link_block(body, mdoc, selected)
826
- data = parse_yaml_data_from_body(body)
827
- data_file = data['file']
828
- return LoadFileNextBlock.new(LoadFile::Reuse, '') unless data_file
829
-
830
- history_state_push(mdoc, data_file, selected)
831
- set_environment_variables(data['vars'])
832
-
833
- LoadFileNextBlock.new(LoadFile::Load, data['block'] || '')
834
- end
835
-
836
- # private
837
-
838
- def parse_yaml_data_from_body(body)
839
- body.any? ? YAML.load(body.join("\n")) : {}
840
- end
841
-
842
- def set_environment_variables(vars)
843
- vars ||= []
844
- vars.each { |key, value| ENV[key] = value.to_s }
845
- end
846
-
847
- # Processes YAML data from the selected menu item, updating delegate objects and optionally printing formatted output.
848
- # @param selected [Hash] Selected item from the menu containing a YAML body.
849
- # @param tgt2 [Hash, nil] An optional target hash to update with YAML data.
850
- # @return [LoadFileNextBlock] An instance indicating the next action for loading files.
851
- def handle_opts_block(selected, tgt2 = nil)
852
- data = YAML.load(selected[:body].join("\n"))
853
- (data || []).each do |key, value|
854
- update_delegate_and_target(key, value, tgt2)
855
- if @delegate_object[:menu_opts_set_format].present?
856
- print_formatted_option(key,
857
- value)
858
- end
859
- end
860
- LoadFileNextBlock.new(LoadFile::Reuse, '')
861
- end
862
-
863
- # private
864
-
865
- def update_delegate_and_target(key, value, tgt2)
866
- sym_key = key.to_sym
867
- @delegate_object[sym_key] = value
868
- tgt2[sym_key] = value if tgt2
869
- end
870
-
871
- def print_formatted_option(key, value)
872
- formatted_str = format(@delegate_object[:menu_opts_set_format],
873
- { key: key, value: value })
874
- print string_send_color(formatted_str, :menu_opts_set_color)
875
- end
876
-
877
720
  def handle_stream(stream, file_type, swap: false)
878
721
  @process_mutex.synchronize do
879
722
  Thread.new do
@@ -896,52 +739,6 @@ module MarkdownExec
896
739
  end
897
740
  end
898
741
 
899
- def wait_for_stream_processing
900
- @process_mutex.synchronize do
901
- @process_cv.wait(@process_mutex)
902
- end
903
- end
904
-
905
- # Partitions the history state from the environment variable based on the document separator.
906
- # @return [Hash] A hash containing two parts: :unit (first part) and :rest (remaining part).
907
- def history_state_partition
908
- history_env_value = ENV.fetch(MDE_HISTORY_ENV_NAME, '')
909
- separator = @delegate_object[:history_document_separator]
910
-
911
- unit, rest = StringUtil.partition_at_first(history_env_value, separator)
912
- { unit: unit, rest: rest }
913
- end
914
-
915
- # Pops the last entry from the history state, updating the delegate object and environment variable.
916
- # It also deletes the required temporary file and updates the run state link history.
917
- def history_state_pop
918
- state = history_state_partition
919
- @delegate_object[:filename] = state[:unit]
920
- ENV[MDE_HISTORY_ENV_NAME] = state[:rest]
921
- delete_required_temp_file
922
- @run_state.link_history.pop
923
- end
924
-
925
- # Updates the history state by pushing a new entry and managing environment variables.
926
- # @param mdoc [Object] The Markdown document object.
927
- # @param data_file [String] The data file to be processed.
928
- # @param selected [Hash] Hash containing the selected block's name.
929
- def history_state_push(mdoc, data_file, selected)
930
- # Construct new history string
931
- new_history = [@delegate_object[:filename],
932
- @delegate_object[:history_document_separator],
933
- ENV.fetch(MDE_HISTORY_ENV_NAME, '')].join
934
-
935
- # Update delegate object and environment variable
936
- @delegate_object[:filename] = data_file
937
- ENV[MDE_HISTORY_ENV_NAME] = new_history
938
-
939
- # Write required blocks to temp file and update run state
940
- write_required_blocks_to_temp_file(mdoc, @delegate_object[:block_name])
941
- @run_state.link_history.push(block_name: selected[:oname],
942
- filename: data_file)
943
- end
944
-
945
742
  # Indents all lines in a given string with a specified indentation string.
946
743
  # @param body [String] A multi-line string to be indented.
947
744
  # @param indent [String] The string used for indentation (default is an empty string).
@@ -952,10 +749,6 @@ module MarkdownExec
952
749
  body.lines.map { |line| indent + line.chomp }.join("\n")
953
750
  end
954
751
 
955
- def initialize_fcb_names(fcb)
956
- fcb.oname = fcb.dname = fcb.title || ''
957
- end
958
-
959
752
  # Initializes variables for regex and other states
960
753
  def initial_state
961
754
  {
@@ -989,6 +782,10 @@ module MarkdownExec
989
782
  write_execution_output_to_file
990
783
  end
991
784
 
785
+ def initialize_fcb_names(fcb)
786
+ fcb.oname = fcb.dname = fcb.title || ''
787
+ end
788
+
992
789
  # Iterates through blocks in a file, applying the provided block to each line.
993
790
  # The iteration only occurs if the file exists.
994
791
  # @yield [Symbol] :filter Yields to obtain selected messages for processing.
@@ -1006,6 +803,31 @@ module MarkdownExec
1006
803
  end
1007
804
  end
1008
805
 
806
+ def link_history_push_and_next(
807
+ curr_block_name:, curr_document_filename:,
808
+ inherited_block_names:, inherited_lines:,
809
+ next_block_name:, next_document_filename:,
810
+ next_load_file:
811
+ )
812
+ @link_history.push(
813
+ LinkState.new(
814
+ block_name: curr_block_name,
815
+ document_filename: curr_document_filename,
816
+ inherited_block_names: inherited_block_names,
817
+ inherited_lines: inherited_lines
818
+ )
819
+ )
820
+ LoadFileLinkState.new(
821
+ next_load_file,
822
+ LinkState.new(
823
+ block_name: next_block_name,
824
+ document_filename: next_document_filename,
825
+ inherited_block_names: inherited_block_names,
826
+ inherited_lines: inherited_lines
827
+ )
828
+ )
829
+ end
830
+
1009
831
  # Loads auto blocks based on delegate object settings and updates if new filename is detected.
1010
832
  # Executes a specified block once per filename.
1011
833
  # @param all_blocks [Array] Array of all block elements.
@@ -1019,46 +841,11 @@ module MarkdownExec
1019
841
  block = block_find(all_blocks, :oname, block_name)
1020
842
  return unless block
1021
843
 
1022
- handle_opts_block(block, @delegate_object)
844
+ update_options_and_trigger_reuse(block, @delegate_object)
1023
845
  @most_recent_loaded_filename = @delegate_object[:filename]
1024
846
  true
1025
847
  end
1026
848
 
1027
- # DebugHelper.d ["HDmm method_name: #{method_name}", "#{first_n_caller_items 1}"]
1028
- def first_n_caller_items(n)
1029
- # Get the call stack
1030
- call_stack = caller
1031
- base_path = File.realpath('.')
1032
-
1033
- # Modify the call stack to remove the base path and keep only the first n items
1034
- call_stack.take(n + 1)[1..].map do |line|
1035
- " . #{line.sub(/^#{Regexp.escape(base_path)}\//, '')}"
1036
- end.join("\n")
1037
- end
1038
-
1039
- # Checks if a history environment variable is set and returns its value if present.
1040
- # @return [String, nil] The value of the history environment variable or nil if not present.
1041
- def history_env_state_exist?
1042
- ENV.fetch(MDE_HISTORY_ENV_NAME, '').present?
1043
- end
1044
-
1045
- # def history_env_state_exist?
1046
- # history = ENV.fetch(MDE_HISTORY_ENV_NAME, '')
1047
- # history.present? ? history : nil
1048
- # end
1049
-
1050
- # If a method is missing, treat it as a key for the @delegate_object.
1051
- def method_missing(method_name, *args, &block)
1052
- if @delegate_object.respond_to?(method_name)
1053
- @delegate_object.send(method_name, *args, &block)
1054
- elsif method_name.to_s.end_with?('=') && args.size == 1
1055
- @delegate_object[method_name.to_s.chop.to_sym] = args.first
1056
- else
1057
- @delegate_object[method_name]
1058
- # super
1059
- end
1060
- end
1061
-
1062
849
  def mdoc_and_blocks_from_nested_files
1063
850
  menu_blocks = blocks_from_nested_files
1064
851
  mdoc = MDoc.new(menu_blocks) do |nopts|
@@ -1074,9 +861,7 @@ module MarkdownExec
1074
861
 
1075
862
  # recreate menu with new options
1076
863
  #
1077
- if load_auto_blocks(all_blocks)
1078
- all_blocks, mdoc = mdoc_and_blocks_from_nested_files
1079
- end
864
+ all_blocks, mdoc = mdoc_and_blocks_from_nested_files if load_auto_blocks(all_blocks)
1080
865
 
1081
866
  menu_blocks = mdoc.fcbs_per_options(@delegate_object)
1082
867
  add_menu_chrome_blocks!(menu_blocks)
@@ -1104,16 +889,41 @@ module MarkdownExec
1104
889
  if @delegate_object[:menu_chrome_format]
1105
890
  format(@delegate_object[:menu_chrome_format], option_value)
1106
891
  else
1107
- option_value
892
+ option_value
893
+ end
894
+ end
895
+
896
+ def merge_lists(*args)
897
+ # Filters out nil values, flattens the arrays, and ensures an empty list is returned if no valid lists are provided
898
+ merged = args.compact.flatten
899
+ merged.empty? ? [] : merged
900
+ end
901
+
902
+ # If a method is missing, treat it as a key for the @delegate_object.
903
+ def method_missing(method_name, *args, &block)
904
+ if @delegate_object.respond_to?(method_name)
905
+ @delegate_object.send(method_name, *args, &block)
906
+ elsif method_name.to_s.end_with?('=') && args.size == 1
907
+ @delegate_object[method_name.to_s.chop.to_sym] = args.first
908
+ else
909
+ @delegate_object[method_name]
910
+ # super
1108
911
  end
1109
912
  end
1110
913
 
1111
- def next_block_name_from_command_line_arguments
1112
- return MenuControl::Repeat unless @delegate_object[:input_cli_rest].present?
914
+ def pop_cli_argument!
915
+ return false unless @delegate_object[:input_cli_rest].present?
916
+
917
+ @cli_block_name = @delegate_object[:input_cli_rest].pop
918
+ true
919
+ end
920
+
921
+ # private
1113
922
 
1114
- # rbp
1115
- @delegate_object[:block_name] = @delegate_object[:input_cli_rest].pop
1116
- MenuControl::Fresh
923
+ def output_color_formatted(data_sym, color_sym)
924
+ formatted_string = string_send_color(@delegate_object[data_sym],
925
+ color_sym)
926
+ @fout.fout formatted_string
1117
927
  end
1118
928
 
1119
929
  def output_execution_result
@@ -1162,13 +972,38 @@ module MarkdownExec
1162
972
  ), level: level
1163
973
  end
1164
974
 
975
+ # private
976
+
977
+ def parse_yaml_data_from_body(body)
978
+ body.any? ? YAML.load(body.join("\n")) : {}
979
+ end
980
+
981
+ # This method handles the back-link operation in the Markdown execution context.
982
+ # It updates the history state and prepares to load the next block.
983
+ #
984
+ # @return [LoadFileLinkState] An object indicating the action to load the next block.
985
+ def pop_link_history_and_trigger_load
986
+ pop = @link_history.pop
987
+ peek = @link_history.peek
988
+ LoadFileLinkState.new(LoadFile::Load, LinkState.new(
989
+ document_filename: pop.document_filename,
990
+ inherited_block_names: peek.inherited_block_names,
991
+ inherited_lines: peek.inherited_lines
992
+ ))
993
+ end
994
+
995
+ def post_execution_process
996
+ initialize_and_save_execution_output
997
+ output_execution_summary
998
+ output_execution_result
999
+ end
1000
+
1165
1001
  # Prepare the blocks menu by adding labels and other necessary details.
1166
1002
  #
1167
1003
  # @param all_blocks [Array<Hash>] The list of blocks from the file.
1168
1004
  # @param opts [Hash] The options hash.
1169
1005
  # @return [Array<Hash>] The updated blocks menu.
1170
1006
  def prepare_blocks_menu(menu_blocks)
1171
- ### replace_consecutive_blanks(menu_blocks).map do |fcb|
1172
1007
  menu_blocks.map do |fcb|
1173
1008
  next if Filter.prepared_not_in_menu?(@delegate_object, fcb,
1174
1009
  %i[block_name_include_match block_name_wrapper_match])
@@ -1189,6 +1024,28 @@ module MarkdownExec
1189
1024
  end.compact
1190
1025
  end
1191
1026
 
1027
+ def print_formatted_option(key, value)
1028
+ formatted_str = format(@delegate_object[:menu_opts_set_format],
1029
+ { key: key, value: value })
1030
+ print string_send_color(formatted_str, :menu_opts_set_color)
1031
+ end
1032
+
1033
+ # private
1034
+
1035
+ def process_block_based_on_type(blocks, btype, fcb)
1036
+ case btype
1037
+ when :blocks
1038
+ blocks.push(get_block_summary(fcb))
1039
+ when :filter
1040
+ %i[blocks line]
1041
+ when :line
1042
+ unless @delegate_object[:no_chrome]
1043
+ create_and_add_chrome_blocks(blocks,
1044
+ fcb)
1045
+ end
1046
+ end
1047
+ end
1048
+
1192
1049
  ##
1193
1050
  # Presents a menu to the user for approving an action and performs additional tasks based on the selection.
1194
1051
  # The function provides options for approval, rejection, copying data to clipboard, or saving data to a file.
@@ -1247,17 +1104,57 @@ module MarkdownExec
1247
1104
  exit 1
1248
1105
  end
1249
1106
 
1250
- def save_to_file(required_lines)
1251
- write_command_file(required_lines)
1252
- @fout.fout "File saved: #{@run_state.saved_filespec}"
1253
- end
1254
-
1255
1107
  # public
1256
1108
 
1109
+ # Handles the processing of a link block in Markdown Execution.
1110
+ # It loads YAML data from the link_block_body content, pushes the state to history,
1111
+ # sets environment variables, and decides on the next block to load.
1112
+ #
1113
+ # @param link_block_body [Array<String>] The body content as an array of strings.
1114
+ # @param mdoc [Object] Markdown document object.
1115
+ # @param selected [FCB] Selected code block.
1116
+ # @return [LoadFileLinkState] Object indicating the next action for file loading.
1117
+ def push_link_history_and_trigger_load(link_block_body, mdoc, selected,
1118
+ link_state = LinkState.new)
1119
+ link_block_data = parse_yaml_data_from_body(link_block_body)
1120
+
1121
+ # load key and values from link block into current environment
1122
+ #
1123
+ (link_block_data['vars'] || []).each do |(key, value)|
1124
+ ENV[key] = value.to_s
1125
+ end
1126
+
1127
+ ## collect blocks specified by block
1128
+ #
1129
+ if mdoc
1130
+ code_info = mdoc.collect_recursively_required_code(
1131
+ selected[:oname],
1132
+ label_format_above: @delegate_object[:shell_code_label_format_above],
1133
+ label_format_below: @delegate_object[:shell_code_label_format_below],
1134
+ block_source: { document_filename: link_state.document_filename }
1135
+ )
1136
+ code_lines = code_info[:code]
1137
+ block_names = code_info[:block_names]
1138
+ else
1139
+ block_names = []
1140
+ code_lines = []
1141
+ end
1142
+
1143
+ next_document_filename = link_block_data['file'] || @delegate_object[:filename]
1144
+ link_history_push_and_next(
1145
+ curr_block_name: selected[:oname],
1146
+ curr_document_filename: @delegate_object[:filename],
1147
+ inherited_block_names: ((link_state&.inherited_block_names || []) + block_names).sort.uniq,
1148
+ inherited_lines: code_merge(link_state&.inherited_lines, code_lines),
1149
+ next_block_name: link_block_data['block'] || '',
1150
+ next_document_filename: next_document_filename,
1151
+ next_load_file: next_document_filename == @delegate_object[:filename] ? LoadFile::Reuse : LoadFile::Load
1152
+ )
1153
+ end
1154
+
1257
1155
  # Reads required code blocks from a temporary file specified by an environment variable.
1258
1156
  # @return [Array<String>] Lines read from the temporary file, or an empty array if file is not found or path is empty.
1259
- def read_required_blocks_from_temp_file
1260
- temp_blocks_file_path = ENV.fetch('MDE_LINK_REQUIRED_FILE', nil)
1157
+ def read_required_blocks_from_temp_file(temp_blocks_file_path)
1261
1158
  return [] if temp_blocks_file_path.to_s.empty?
1262
1159
 
1263
1160
  if File.exist?(temp_blocks_file_path)
@@ -1269,6 +1166,29 @@ module MarkdownExec
1269
1166
  end
1270
1167
  end
1271
1168
 
1169
+ def runtime_exception(exception_sym, name, items)
1170
+ if @delegate_object[exception_sym] != 0
1171
+ data = { name: name, detail: items.join(', ') }
1172
+ warn(
1173
+ format(
1174
+ @delegate_object.fetch(:exception_format_name, "\n%{name}"),
1175
+ data
1176
+ ).send(@delegate_object.fetch(:exception_color_name, :red)) +
1177
+ format(
1178
+ @delegate_object.fetch(:exception_format_detail, " - %{detail}\n"),
1179
+ data
1180
+ ).send(@delegate_object.fetch(:exception_color_detail, :yellow))
1181
+ )
1182
+ end
1183
+ return unless (@delegate_object[exception_sym]).positive?
1184
+
1185
+ exit @delegate_object[exception_sym]
1186
+ end
1187
+
1188
+ def safely_remove_file(path)
1189
+ FileUtils.rm_f(path)
1190
+ end
1191
+
1272
1192
  # Evaluates the given string as Ruby code and rescues any StandardErrors.
1273
1193
  # If an error occurs, it calls the error_handler method with 'safeval'.
1274
1194
  # @param str [String] The string to be evaluated.
@@ -1279,11 +1199,10 @@ module MarkdownExec
1279
1199
  error_handler('safeval')
1280
1200
  end
1281
1201
 
1282
- # def safeval(str)
1283
- # eval(str)
1284
- # rescue StandardError
1285
- # error_handler('safeval')
1286
- # end
1202
+ def save_to_file(required_lines)
1203
+ write_command_file(required_lines)
1204
+ @fout.fout "File saved: #{@run_state.saved_filespec}"
1205
+ end
1287
1206
 
1288
1207
  # Select and execute a code block from a Markdown document.
1289
1208
  #
@@ -1291,26 +1210,27 @@ module MarkdownExec
1291
1210
  # Markdown document, obtain approval, and execute the chosen block of code.
1292
1211
  #
1293
1212
  # @return [Nil] Returns nil if no code block is selected or an error occurs.
1294
- def select_approve_and_execute_block(_execute: true)
1213
+ def select_execute_bash_and_special_blocks(_execute: true)
1295
1214
  @menu_base_options = @delegate_object
1296
- repeat_menu = @menu_base_options[:block_name].present? ? MenuControl::Fresh : MenuControl::Repeat
1297
- load_file_next_block = LoadFileNextBlock.new(LoadFile::Reuse)
1298
- default = nil
1299
-
1300
- @menu_state_filename = @menu_base_options[:filename]
1301
- @menu_state_block_name = @menu_base_options[:block_name]
1215
+ link_state = LinkState.new(
1216
+ block_name: @delegate_object[:block_name],
1217
+ document_filename: @delegate_object[:filename]
1218
+ )
1219
+ block_name_from_cli = link_state.block_name.present?
1220
+ @cli_block_name = link_state.block_name
1221
+ load_file = nil
1222
+ menu_default_dname = nil
1302
1223
 
1303
1224
  loop do
1304
1225
  loop do
1305
1226
  @delegate_object = @menu_base_options.dup
1306
- @menu_base_options[:filename] = @menu_state_filename
1307
- # rbp
1308
- @menu_base_options[:block_name] = @menu_state_block_name
1309
- @menu_state_filename = nil
1310
- @menu_state_block_name = nil
1311
1227
  @menu_user_clicked_back_link = false
1228
+ @delegate_object[:filename] = link_state.document_filename
1229
+ link_state.block_name = @delegate_object[:block_name] =
1230
+ block_name_from_cli ? @cli_block_name : link_state.block_name
1312
1231
 
1313
1232
  blocks_in_file, menu_blocks, mdoc = mdoc_menu_and_blocks_from_nested_files
1233
+
1314
1234
  if @delegate_object[:dump_blocks_in_file]
1315
1235
  warn format_and_highlight_dependencies(
1316
1236
  compact_and_index_hash(blocks_in_file),
@@ -1323,12 +1243,13 @@ module MarkdownExec
1323
1243
  label: 'menu_blocks'
1324
1244
  )
1325
1245
  end
1246
+
1326
1247
  block_state = command_or_user_selected_block(blocks_in_file,
1327
- menu_blocks, default)
1248
+ menu_blocks, menu_default_dname)
1328
1249
  return if block_state.state == MenuState::EXIT
1329
1250
 
1330
1251
  if block_state.block.nil?
1331
- warn_format('select_approve_and_execute_block', "Block not found -- #{@delegate_object[:block_name]}",
1252
+ warn_format('select_execute_bash_and_special_blocks', "Block not found -- #{@delegate_object[:block_name]}",
1332
1253
  { abort: true })
1333
1254
  # error_handler("Block not found -- #{opts[:block_name]}", { abort: true })
1334
1255
  end
@@ -1337,18 +1258,20 @@ module MarkdownExec
1337
1258
  warn block_state.block.to_yaml.sub(/^(?:---\n)?/, "Block:\n")
1338
1259
  end
1339
1260
 
1340
- # rbp
1341
- load_file_next_block = approve_and_execute_block(block_state.block,
1342
- mdoc)
1343
- default = load_file_next_block.load_file == LoadFile::Load ? nil : @delegate_object[:block_name]
1344
- # rbp
1345
- @menu_base_options[:block_name] =
1346
- @delegate_object[:block_name] = load_file_next_block.next_block
1347
- @menu_base_options[:filename] = @delegate_object[:filename]
1261
+ load_file_link_state = execute_bash_and_special_blocks(
1262
+ block_state.block,
1263
+ mdoc,
1264
+ link_state,
1265
+ block_source: { document_filename: @delegate_object[:filename] }
1266
+ )
1267
+ load_file = load_file_link_state.load_file
1268
+ link_state = load_file_link_state.link_state
1269
+ # if the same menu is being displayed, collect the display name of the selected menu item for use as the default item
1270
+ menu_default_dname = load_file == LoadFile::Load ? nil : block_state.block[:dname]
1348
1271
 
1349
1272
  # user prompt to exit if the menu will be displayed again
1350
1273
  #
1351
- if repeat_menu == MenuControl::Repeat &&
1274
+ if !block_name_from_cli &&
1352
1275
  block_state.block[:shell] == BlockType::BASH &&
1353
1276
  @delegate_object[:pause_after_script_execution] &&
1354
1277
  prompt_select_continue == MenuState::EXIT
@@ -1357,19 +1280,15 @@ module MarkdownExec
1357
1280
 
1358
1281
  # exit current document/menu if loading next document or single block_name was specified
1359
1282
  #
1360
- if block_state.state == MenuState::CONTINUE && load_file_next_block.load_file == LoadFile::Load
1361
- break
1362
- end
1363
- break if repeat_menu == MenuControl::Fresh
1283
+ break if block_state.state == MenuState::CONTINUE && load_file == LoadFile::Load
1284
+ break if block_name_from_cli
1364
1285
  end
1365
- break if load_file_next_block.load_file == LoadFile::Reuse
1286
+ break if load_file == LoadFile::Reuse
1366
1287
 
1367
- repeat_menu = next_block_name_from_command_line_arguments
1368
- @menu_state_filename = @menu_base_options[:filename]
1369
- @menu_state_block_name = @menu_base_options[:block_name]
1288
+ block_name_from_cli = pop_cli_argument!
1370
1289
  end
1371
1290
  rescue StandardError
1372
- error_handler('select_approve_and_execute_block',
1291
+ error_handler('select_execute_bash_and_special_blocks',
1373
1292
  { abort: true })
1374
1293
  end
1375
1294
 
@@ -1386,8 +1305,7 @@ module MarkdownExec
1386
1305
 
1387
1306
  item.merge(
1388
1307
  if selection == menu_chrome_colored_option(:menu_option_back_name)
1389
- { option: selection, curr: @hs_curr, rest: @hs_rest,
1390
- shell: BlockType::LINK }
1308
+ { option: selection, shell: BlockType::LINK }
1391
1309
  elsif selection == menu_chrome_colored_option(:menu_option_exit_name)
1392
1310
  { option: selection }
1393
1311
  else
@@ -1400,6 +1318,35 @@ module MarkdownExec
1400
1318
  error_handler('select_option_with_metadata')
1401
1319
  end
1402
1320
 
1321
+ def set_environment_variables_for_block(selected)
1322
+ YAML.load(selected[:body].join("\n")).each do |key, value|
1323
+ ENV[key] = value.to_s
1324
+ next unless @delegate_object[:menu_vars_set_format].present?
1325
+
1326
+ formatted_string = format(@delegate_object[:menu_vars_set_format],
1327
+ { key: key, value: value })
1328
+ print string_send_color(formatted_string, :menu_vars_set_color)
1329
+ end
1330
+ end
1331
+
1332
+ def set_environment_variables_per_array(vars)
1333
+ vars ||= []
1334
+ vars.each { |key, value| ENV[key] = value.to_s }
1335
+ end
1336
+
1337
+ def set_file_permissions(file_path, chmod_value)
1338
+ File.chmod(chmod_value, file_path)
1339
+ end
1340
+
1341
+ def set_script_block_name(selected)
1342
+ @run_state.script_block_name = selected[:oname]
1343
+ end
1344
+
1345
+ def should_add_back_option?
1346
+ @delegate_object[:menu_with_back] && @link_history.prior_state_exist?
1347
+ # @delegate_object[:menu_with_back] && link_history_prior_state_exist?
1348
+ end
1349
+
1403
1350
  # Initializes a new fenced code block (FCB) object based on the provided line and heading information.
1404
1351
  # @param line [String] The line initiating the fenced block.
1405
1352
  # @param headings [Array<String>] Current headings hierarchy.
@@ -1459,6 +1406,12 @@ module MarkdownExec
1459
1406
  )
1460
1407
  end
1461
1408
 
1409
+ def update_delegate_and_target(key, value, tgt2)
1410
+ sym_key = key.to_sym
1411
+ @delegate_object[sym_key] = value
1412
+ tgt2[sym_key] = value if tgt2
1413
+ end
1414
+
1462
1415
  # Updates the hierarchy of document headings based on the given line.
1463
1416
  # Utilizes regular expressions to identify heading levels.
1464
1417
  # @param line [String] The line of text to check for headings.
@@ -1527,12 +1480,9 @@ module MarkdownExec
1527
1480
  ## add line to fenced code block
1528
1481
  # remove fcb indent if possible
1529
1482
  #
1530
- # if nested_line[:depth].zero? || opts[:menu_include_imported_blocks]
1531
- # if add_import_block?(nested_line)
1532
1483
  state[:fcb].body += [
1533
1484
  line.chomp.sub(/^#{state[:fcb].indent}/, '')
1534
1485
  ]
1535
- # end
1536
1486
  elsif nested_line[:depth].zero? || @delegate_object[:menu_include_imported_notes]
1537
1487
  # add line if it is depth 0 or option allows it
1538
1488
  #
@@ -1555,10 +1505,29 @@ module MarkdownExec
1555
1505
  yield_to_block_if_applicable(fcb, selected_messages, &block)
1556
1506
  end
1557
1507
 
1508
+ # Processes YAML data from the selected menu item, updating delegate objects and optionally printing formatted output.
1509
+ # @param selected [Hash] Selected item from the menu containing a YAML body.
1510
+ # @param tgt2 [Hash, nil] An optional target hash to update with YAML data.
1511
+ # @return [LoadFileLinkState] An instance indicating the next action for loading files.
1512
+ def update_options_and_trigger_reuse(selected, tgt2 = nil, link_state = LinkState.new)
1513
+ data = YAML.load(selected[:body].join("\n"))
1514
+ (data || []).each do |key, value|
1515
+ update_delegate_and_target(key, value, tgt2)
1516
+ print_formatted_option(key, value) if @delegate_object[:menu_opts_set_format].present?
1517
+ end
1518
+ link_state.block_name = nil
1519
+ LoadFileLinkState.new(LoadFile::Reuse, link_state)
1520
+ end
1521
+
1522
+ def wait_for_stream_processing
1523
+ @process_mutex.synchronize do
1524
+ @process_cv.wait(@process_mutex)
1525
+ end
1526
+ end
1527
+
1558
1528
  def wait_for_user_selected_block(all_blocks, menu_blocks, default)
1559
1529
  block_state = wait_for_user_selection(all_blocks, menu_blocks, default)
1560
1530
  handle_block_state(block_state)
1561
-
1562
1531
  block_state
1563
1532
  rescue StandardError
1564
1533
  error_handler('wait_for_user_selected_block')
@@ -1590,7 +1559,6 @@ module MarkdownExec
1590
1559
  def write_command_file(required_lines)
1591
1560
  return unless @delegate_object[:save_executed_script]
1592
1561
 
1593
- # rbp
1594
1562
  time_now = Time.now.utc
1595
1563
  @run_state.saved_script_filename =
1596
1564
  SavedAsset.script_name(blockname: @delegate_object[:block_name],
@@ -1613,7 +1581,7 @@ module MarkdownExec
1613
1581
  "# time: #{time_now}\n" \
1614
1582
  "#{required_lines.flatten.join("\n")}\n"
1615
1583
 
1616
- create_and_write_file_with_permissions(
1584
+ create_file_and_write_string_with_permissions(
1617
1585
  @run_state.saved_filespec,
1618
1586
  content,
1619
1587
  @delegate_object[:saved_script_chmod]
@@ -1622,6 +1590,10 @@ module MarkdownExec
1622
1590
  error_handler('write_command_file')
1623
1591
  end
1624
1592
 
1593
+ def save_executed_script_if_specified(lines)
1594
+ write_command_file(lines) if @delegate_object[:save_executed_script]
1595
+ end
1596
+
1625
1597
  def write_execution_output_to_file
1626
1598
  FileUtils.mkdir_p File.dirname(@delegate_object[:logged_stdout_filespec])
1627
1599
 
@@ -1641,7 +1613,7 @@ module MarkdownExec
1641
1613
  #
1642
1614
  # @param mdoc [Object] The Markdown document object.
1643
1615
  # @param block_name [String] The name of the block to collect code for.
1644
- def write_required_blocks_to_temp_file(mdoc, block_name)
1616
+ def write_required_blocks_to_file(mdoc, block_name, temp_file_path, import_filename: nil)
1645
1617
  c1 = if mdoc
1646
1618
  mdoc.collect_recursively_required_code(
1647
1619
  block_name,
@@ -1652,10 +1624,16 @@ module MarkdownExec
1652
1624
  []
1653
1625
  end
1654
1626
 
1655
- code_blocks = (read_required_blocks_from_temp_file +
1627
+ code_blocks = (read_required_blocks_from_temp_file(import_filename) +
1656
1628
  c1).join("\n")
1657
1629
 
1658
- create_temp_file_with_code(code_blocks)
1630
+ write_code_to_file(code_blocks, temp_file_path)
1631
+ end
1632
+
1633
+ # Writes the provided code blocks to a file.
1634
+ # @param code_blocks [String] Code blocks to write into the file.
1635
+ def write_code_to_file(content, path)
1636
+ File.write(path, content)
1659
1637
  end
1660
1638
 
1661
1639
  # Yields a line as a new block if the selected message type includes :line.
@@ -1698,7 +1676,7 @@ if $PROGRAM_NAME == __FILE__
1698
1676
  @mdoc = mock('MarkdownDocument')
1699
1677
  end
1700
1678
 
1701
- def test_calling_execute_approved_block_calls_command_execute_with_argument_args_value
1679
+ def test_calling_execute_required_lines_calls_command_execute_with_argument_args_value
1702
1680
  pigeon = 'E'
1703
1681
  obj = {
1704
1682
  output_execution_label_format: '',
@@ -1715,46 +1693,33 @@ if $PROGRAM_NAME == __FILE__
1715
1693
  args: pigeon
1716
1694
  )
1717
1695
 
1718
- # Call method opts_execute_approved_block
1719
- c.execute_approved_block([], MarkdownExec::FCB.new)
1696
+ # Call method opts_execute_required_lines
1697
+ c.execute_required_lines([])
1720
1698
  end
1721
1699
 
1722
1700
  # Test case for empty body
1723
- def test_handle_link_block_with_empty_body
1724
- assert_equal LoadFileNextBlock.new(LoadFile::Reuse, ''),
1725
- @hd.handle_link_block([], nil, false)
1701
+ def test_push_link_history_and_trigger_load_with_empty_body
1702
+ assert_equal LoadFile::Reuse,
1703
+ @hd.push_link_history_and_trigger_load([], nil, FCB.new).load_file
1726
1704
  end
1727
1705
 
1728
1706
  # Test case for non-empty body without 'file' key
1729
- def test_handle_link_block_without_file_key
1707
+ def test_push_link_history_and_trigger_load_without_file_key
1730
1708
  body = ["vars:\n KEY: VALUE"]
1731
- assert_equal LoadFileNextBlock.new(LoadFile::Reuse, ''),
1732
- @hd.handle_link_block(body, nil, false)
1709
+ assert_equal LoadFile::Reuse,
1710
+ @hd.push_link_history_and_trigger_load(body, nil, FCB.new).load_file
1733
1711
  end
1734
1712
 
1735
1713
  # Test case for non-empty body with 'file' key
1736
- def test_handle_link_block_with_file_key
1714
+ def test_push_link_history_and_trigger_load_with_file_key
1737
1715
  body = ["file: sample_file\nblock: sample_block\nvars:\n KEY: VALUE"]
1738
- expected_result = LoadFileNextBlock.new(LoadFile::Load,
1739
- 'sample_block')
1740
- # mdoc = MDoc.new()
1716
+ expected_result = LoadFileLinkState.new(LoadFile::Load,
1717
+ LinkState.new(block_name: 'sample_block',
1718
+ document_filename: 'sample_file',
1719
+ inherited_lines: []))
1741
1720
  assert_equal expected_result,
1742
- @hd.handle_link_block(body, nil, FCB.new)
1743
- end
1744
-
1745
- def test_history_env_state_exist_with_value
1746
- ENV[MDE_HISTORY_ENV_NAME] = 'history_value'
1747
- assert @hd.history_env_state_exist?
1748
- end
1749
-
1750
- def test_history_env_state_exist_without_value
1751
- ENV[MDE_HISTORY_ENV_NAME] = ''
1752
- refute @hd.history_env_state_exist?
1753
- end
1754
-
1755
- def test_history_env_state_exist_not_set
1756
- ENV.delete(MDE_HISTORY_ENV_NAME)
1757
- refute @hd.history_env_state_exist?
1721
+ @hd.push_link_history_and_trigger_load(body, nil, FCB.new(block_name: 'sample_block',
1722
+ filename: 'sample_file'))
1758
1723
  end
1759
1724
 
1760
1725
  def test_indent_all_lines_with_indent
@@ -1778,22 +1743,6 @@ if $PROGRAM_NAME == __FILE__
1778
1743
  assert_equal body, @hd.indent_all_lines(body, indent)
1779
1744
  end
1780
1745
 
1781
- def test_read_required_blocks_from_temp_file
1782
- Tempfile.create do |file|
1783
- file.write("Line 1\nLine 2")
1784
- file.rewind
1785
- ENV['MDE_LINK_REQUIRED_FILE'] = file.path
1786
-
1787
- result = @hd.read_required_blocks_from_temp_file
1788
- assert_equal ['Line 1', 'Line 2'], result
1789
- end
1790
- end
1791
-
1792
- def test_read_required_blocks_from_temp_file_no_file
1793
- ENV['MDE_LINK_REQUIRED_FILE'] = nil
1794
- assert_empty @hd.read_required_blocks_from_temp_file
1795
- end
1796
-
1797
1746
  def test_safeval_successful_evaluation
1798
1747
  assert_equal 4, @hd.safeval('2 + 2')
1799
1748
  end
@@ -1823,8 +1772,6 @@ if $PROGRAM_NAME == __FILE__
1823
1772
  }
1824
1773
  ]
1825
1774
 
1826
- # hd = HashDelegator.new
1827
-
1828
1775
  # iterate over the input and output data and
1829
1776
  # assert that the method sets the title as expected
1830
1777
  input_output_data.each do |data|
@@ -1871,7 +1818,7 @@ if $PROGRAM_NAME == __FILE__
1871
1818
 
1872
1819
  assert_empty menu_blocks
1873
1820
  end
1874
- end # class TestHashDelegator
1821
+ end
1875
1822
 
1876
1823
  class TestHashDelegatorBlockFind < Minitest::Test
1877
1824
  def setup
@@ -1895,7 +1842,7 @@ if $PROGRAM_NAME == __FILE__
1895
1842
  result = @hd.block_find(blocks, :key, 'value3', 'default')
1896
1843
  assert_equal 'default', result
1897
1844
  end
1898
- end # class TestHashDelegator
1845
+ end
1899
1846
 
1900
1847
  class TestHashDelegatorBlocksFromNestedFiles < Minitest::Test
1901
1848
  def setup
@@ -1922,7 +1869,7 @@ if $PROGRAM_NAME == __FILE__
1922
1869
 
1923
1870
  assert_kind_of Array, result
1924
1871
  end
1925
- end # class TestHashDelegator
1872
+ end
1926
1873
 
1927
1874
  class TestHashDelegatorCollectRequiredCodeLines < Minitest::Test
1928
1875
  def setup
@@ -1938,13 +1885,11 @@ if $PROGRAM_NAME == __FILE__
1938
1885
  def test_collect_required_code_lines_with_vars
1939
1886
  YAML.stubs(:load).returns({ 'key' => 'value' })
1940
1887
  @mdoc.stubs(:collect_recursively_required_code).returns({ code: ['code line'] })
1941
- ENV.stubs(:[]=)
1942
-
1943
- result = @hd.collect_required_code_lines(@mdoc, @selected)
1888
+ result = @hd.collect_required_code_lines(@mdoc, @selected, block_source: {})
1944
1889
 
1945
1890
  assert_equal ['code line'], result
1946
1891
  end
1947
- end # class TestHashDelegator
1892
+ end
1948
1893
 
1949
1894
  class TestHashDelegatorCommandOrUserSelectedBlock < Minitest::Test
1950
1895
  def setup
@@ -1975,7 +1920,7 @@ if $PROGRAM_NAME == __FILE__
1975
1920
  assert_equal block_state.block, result.block
1976
1921
  assert_equal :some_state, result.state
1977
1922
  end
1978
- end # class TestHashDelegator
1923
+ end
1979
1924
 
1980
1925
  class TestHashDelegatorCountBlockInFilename < Minitest::Test
1981
1926
  def setup
@@ -2004,7 +1949,7 @@ if $PROGRAM_NAME == __FILE__
2004
1949
 
2005
1950
  assert_equal 0, count
2006
1951
  end
2007
- end # class TestHashDelegator
1952
+ end
2008
1953
 
2009
1954
  class TestHashDelegatorCreateAndWriteFile < Minitest::Test
2010
1955
  def setup
@@ -2015,7 +1960,7 @@ if $PROGRAM_NAME == __FILE__
2015
1960
  File.stubs(:chmod)
2016
1961
  end
2017
1962
 
2018
- def test_create_and_write_file_with_permissions
1963
+ def test_create_file_and_write_string_with_permissions
2019
1964
  file_path = '/path/to/file'
2020
1965
  content = 'sample content'
2021
1966
  chmod_value = 0o644
@@ -2024,8 +1969,8 @@ if $PROGRAM_NAME == __FILE__
2024
1969
  File.expects(:write).with(file_path, content).once
2025
1970
  File.expects(:chmod).with(chmod_value, file_path).once
2026
1971
 
2027
- @hd.create_and_write_file_with_permissions(file_path, content,
2028
- chmod_value)
1972
+ @hd.create_file_and_write_string_with_permissions(file_path, content,
1973
+ chmod_value)
2029
1974
 
2030
1975
  assert true # Placeholder for actual test assertions
2031
1976
  end
@@ -2039,70 +1984,12 @@ if $PROGRAM_NAME == __FILE__
2039
1984
  File.expects(:write).with(file_path, content).once
2040
1985
  File.expects(:chmod).never
2041
1986
 
2042
- @hd.create_and_write_file_with_permissions(file_path, content,
2043
- chmod_value)
2044
-
2045
- assert true # Placeholder for actual test assertions
2046
- end
2047
- end # class TestHashDelegator
2048
-
2049
- class TestHashDelegatorCreateTempFile < Minitest::Test
2050
- def setup
2051
- @hd = HashDelegator.new
2052
- @temp_file_path = '/tmp/tempfile'
2053
- end
2054
-
2055
- def test_create_temp_file_with_code
2056
- Dir::Tmpname.stubs(:create).returns(@temp_file_path)
2057
- File.stubs(:write).with(@temp_file_path, 'code_blocks')
2058
- # ENV.expects(:[]=).with('MDE_LINK_REQUIRED_FILE', @temp_file_path)
2059
-
2060
- @hd.create_temp_file_with_code('code_blocks')
2061
-
2062
- assert true # Placeholder for actual test assertions
2063
- end
2064
- end # class TestHashDelegator
2065
-
2066
- class TestHashDelegatorDeleteRequiredTempFile < Minitest::Test
2067
- def setup
2068
- @hd = HashDelegator.new
2069
- @hd.stubs(:error_handler)
2070
- @hd.stubs(:clear_required_file)
2071
- FileUtils.stubs(:rm_f)
2072
- end
2073
-
2074
- def test_delete_required_temp_file_with_existing_file
2075
- ENV.stubs(:fetch).with('MDE_LINK_REQUIRED_FILE',
2076
- nil).returns('/path/to/temp_file')
2077
- FileUtils.expects(:rm_f).with('/path/to/temp_file').once
2078
- @hd.expects(:clear_required_file).once
2079
-
2080
- @hd.delete_required_temp_file
2081
-
2082
- assert true # Placeholder for actual test assertions
2083
- end
2084
-
2085
- def test_delete_required_temp_file_with_no_file
2086
- ENV.stubs(:fetch).with('MDE_LINK_REQUIRED_FILE', nil).returns(nil)
2087
- FileUtils.expects(:rm_f).never
2088
- @hd.expects(:clear_required_file).never
2089
-
2090
- @hd.delete_required_temp_file
2091
-
2092
- assert true # Placeholder for actual test assertions
2093
- end
2094
-
2095
- def test_delete_required_temp_file_with_error
2096
- ENV.stubs(:fetch).with('MDE_LINK_REQUIRED_FILE',
2097
- nil).returns('/path/to/temp_file')
2098
- FileUtils.stubs(:rm_f).raises(StandardError)
2099
- @hd.expects(:error_handler).with('delete_required_temp_file').once
2100
-
2101
- @hd.delete_required_temp_file
1987
+ @hd.create_file_and_write_string_with_permissions(file_path, content,
1988
+ chmod_value)
2102
1989
 
2103
1990
  assert true # Placeholder for actual test assertions
2104
1991
  end
2105
- end # class TestHashDelegator
1992
+ end
2106
1993
 
2107
1994
  class TestHashDelegatorDetermineBlockState < Minitest::Test
2108
1995
  def setup
@@ -2137,7 +2024,7 @@ if $PROGRAM_NAME == __FILE__
2137
2024
  assert_equal MenuState::CONTINUE, result.state
2138
2025
  assert_equal selected_option, result.block
2139
2026
  end
2140
- end # class TestHashDelegator
2027
+ end
2141
2028
 
2142
2029
  class TestHashDelegatorDisplayRequiredCode < Minitest::Test
2143
2030
  def setup
@@ -2158,7 +2045,7 @@ if $PROGRAM_NAME == __FILE__
2158
2045
  # Verifying that fout is called for each line and for header & footer
2159
2046
  assert true # Placeholder for actual test assertions
2160
2047
  end
2161
- end # class TestHashDelegator
2048
+ end
2162
2049
 
2163
2050
  class TestHashDelegatorFetchColor < Minitest::Test
2164
2051
  def setup
@@ -2189,7 +2076,7 @@ if $PROGRAM_NAME == __FILE__
2189
2076
 
2190
2077
  assert_equal 'Default Colored String', result
2191
2078
  end
2192
- end # class TestHashDelegator
2079
+ end
2193
2080
 
2194
2081
  class TestHashDelegatorFormatReferencesSendColor < Minitest::Test
2195
2082
  def setup
@@ -2220,7 +2107,7 @@ if $PROGRAM_NAME == __FILE__
2220
2107
 
2221
2108
  assert_equal 'Default Colored String', result
2222
2109
  end
2223
- end # class TestHashDelegator
2110
+ end
2224
2111
 
2225
2112
  class TestHashDelegatorFormatExecutionStreams < Minitest::Test
2226
2113
  def setup
@@ -2253,7 +2140,7 @@ if $PROGRAM_NAME == __FILE__
2253
2140
 
2254
2141
  assert_equal '', result
2255
2142
  end
2256
- end # class TestHashDelegator
2143
+ end
2257
2144
 
2258
2145
  class TestHashDelegatorHandleBackLink < Minitest::Test
2259
2146
  def setup
@@ -2261,18 +2148,18 @@ if $PROGRAM_NAME == __FILE__
2261
2148
  @hd.stubs(:history_state_pop)
2262
2149
  end
2263
2150
 
2264
- def test_handle_back_link
2151
+ def test_pop_link_history_and_trigger_load
2265
2152
  # Verifying that history_state_pop is called
2266
- @hd.expects(:history_state_pop).once
2153
+ # @hd.expects(:history_state_pop).once
2267
2154
 
2268
- result = @hd.handle_back_link
2155
+ result = @hd.pop_link_history_and_trigger_load
2269
2156
 
2270
- # Asserting the result is an instance of LoadFileNextBlock
2271
- assert_instance_of LoadFileNextBlock, result
2157
+ # Asserting the result is an instance of LoadFileLinkState
2158
+ assert_instance_of LoadFileLinkState, result
2272
2159
  assert_equal LoadFile::Load, result.load_file
2273
- assert_equal '', result.next_block
2160
+ assert_nil result.link_state.block_name
2274
2161
  end
2275
- end # class TestHashDelegator
2162
+ end
2276
2163
 
2277
2164
  class TestHashDelegatorHandleBlockState < Minitest::Test
2278
2165
  def setup
@@ -2311,7 +2198,7 @@ if $PROGRAM_NAME == __FILE__
2311
2198
  assert_nil @hd.instance_variable_get(:@delegate_object)[:block_name]
2312
2199
  assert_nil @hd.instance_variable_get(:@menu_user_clicked_back_link)
2313
2200
  end
2314
- end # class TestHashDelegator
2201
+ end
2315
2202
 
2316
2203
  class TestHashDelegatorHandleGenericBlock < Minitest::Test
2317
2204
  def setup
@@ -2320,7 +2207,7 @@ if $PROGRAM_NAME == __FILE__
2320
2207
  @selected_item = mock('FCB')
2321
2208
  end
2322
2209
 
2323
- def test_handle_generic_block_without_user_approval
2210
+ def test_compile_execute_bash_and_special_blocks_and_trigger_reuse_without_user_approval
2324
2211
  # Mock the delegate object configuration
2325
2212
  @hd.instance_variable_set(:@delegate_object,
2326
2213
  { output_script: false,
@@ -2330,7 +2217,7 @@ if $PROGRAM_NAME == __FILE__
2330
2217
  # Expectations and assertions go here
2331
2218
  end
2332
2219
 
2333
- def test_handle_generic_block_with_user_approval
2220
+ def test_compile_execute_bash_and_special_blocks_and_trigger_reuse_with_user_approval
2334
2221
  # Mock the delegate object configuration
2335
2222
  @hd.instance_variable_set(:@delegate_object,
2336
2223
  { output_script: false,
@@ -2340,7 +2227,7 @@ if $PROGRAM_NAME == __FILE__
2340
2227
  # Expectations and assertions go here
2341
2228
  end
2342
2229
 
2343
- def test_handle_generic_block_with_output_script
2230
+ def test_compile_execute_bash_and_special_blocks_and_trigger_reuse_with_output_script
2344
2231
  # Mock the delegate object configuration
2345
2232
  @hd.instance_variable_set(:@delegate_object,
2346
2233
  { output_script: true,
@@ -2361,25 +2248,25 @@ if $PROGRAM_NAME == __FILE__
2361
2248
  @hd.stubs(:print)
2362
2249
  end
2363
2250
 
2364
- def test_handle_opts_block
2251
+ def test_update_options_and_trigger_reuse
2365
2252
  selected = { body: ['option1: value1'] }
2366
2253
  tgt2 = {}
2367
2254
 
2368
- result = @hd.handle_opts_block(selected, tgt2)
2255
+ result = @hd.update_options_and_trigger_reuse(selected, tgt2)
2369
2256
 
2370
- assert_instance_of LoadFileNextBlock, result
2257
+ assert_instance_of LoadFileLinkState, result
2371
2258
  assert_equal 'value1',
2372
2259
  @hd.instance_variable_get(:@delegate_object)[:option1]
2373
2260
  assert_equal 'value1', tgt2[:option1]
2374
2261
  end
2375
2262
 
2376
- def test_handle_opts_block_without_format
2263
+ def test_update_options_and_trigger_reuse_without_format
2377
2264
  selected = { body: ['option2: value2'] }
2378
2265
  @hd.instance_variable_set(:@delegate_object, {})
2379
2266
 
2380
- result = @hd.handle_opts_block(selected)
2267
+ result = @hd.update_options_and_trigger_reuse(selected)
2381
2268
 
2382
- assert_instance_of LoadFileNextBlock, result
2269
+ assert_instance_of LoadFileLinkState, result
2383
2270
  assert_equal 'value2',
2384
2271
  @hd.instance_variable_get(:@delegate_object)[:option2]
2385
2272
  end
@@ -2422,94 +2309,6 @@ if $PROGRAM_NAME == __FILE__
2422
2309
  end
2423
2310
  end
2424
2311
 
2425
- class TestHashDelegatorHistoryStatePartition < Minitest::Test
2426
- def setup
2427
- @hd = HashDelegator.new
2428
- @hd.instance_variable_set(:@delegate_object, {
2429
- history_document_separator: '|'
2430
- })
2431
- end
2432
-
2433
- def test_history_state_partition_with_value
2434
- ENV[MDE_HISTORY_ENV_NAME] = 'part1|part2'
2435
-
2436
- result = @hd.history_state_partition
2437
- assert_equal({ unit: 'part1', rest: 'part2' }, result)
2438
- end
2439
-
2440
- def test_history_state_partition_with_no_separator
2441
- ENV[MDE_HISTORY_ENV_NAME] = 'onlypart'
2442
-
2443
- result = @hd.history_state_partition
2444
- assert_equal({ unit: 'onlypart', rest: '' }, result)
2445
- end
2446
-
2447
- def test_history_state_partition_with_empty_env
2448
- ENV[MDE_HISTORY_ENV_NAME] = ''
2449
-
2450
- result = @hd.history_state_partition
2451
- assert_equal({ unit: '', rest: '' }, result)
2452
- end
2453
- end
2454
-
2455
- class TestHashDelegatorHistoryStatePop < Minitest::Test
2456
- def setup
2457
- @hd = HashDelegator.new
2458
- @hd.instance_variable_set(:@delegate_object,
2459
- { filename: 'initial.md' })
2460
- @hd.instance_variable_set(:@run_state,
2461
- OpenStruct.new(link_history: [{ block_name: 'block1',
2462
- filename: 'file1.md' }]))
2463
- @hd.stubs(:history_state_partition).returns({ unit: 'file2.md',
2464
- rest: 'history_data' })
2465
- @hd.stubs(:delete_required_temp_file)
2466
- end
2467
-
2468
- def test_history_state_pop
2469
- ENV[MDE_HISTORY_ENV_NAME] = 'some_history'
2470
-
2471
- @hd.history_state_pop
2472
-
2473
- assert_equal 'file2.md',
2474
- @hd.instance_variable_get(:@delegate_object)[:filename]
2475
- assert_equal 'history_data',
2476
- ENV.fetch(MDE_HISTORY_ENV_NAME, nil)
2477
- assert_empty @hd.instance_variable_get(:@run_state).link_history
2478
- end
2479
- end
2480
-
2481
- class TestHashDelegatorHistoryStatePush < Minitest::Test
2482
- def setup
2483
- @hd = HashDelegator.new
2484
- @hd.instance_variable_set(:@delegate_object, {
2485
- filename: 'test.md',
2486
- block_name: 'test_block',
2487
- history_document_separator: '||'
2488
- })
2489
- @hd.instance_variable_set(:@run_state,
2490
- OpenStruct.new(link_history: []))
2491
- @hd.stubs(:write_required_blocks_to_temp_file)
2492
- end
2493
-
2494
- def test_history_state_push
2495
- mdoc = 'markdown content'
2496
- data_file = 'data.md'
2497
- selected = { oname: 'selected_block' }
2498
-
2499
- ENV[MDE_HISTORY_ENV_NAME] = 'existing_history'
2500
-
2501
- @hd.history_state_push(mdoc, data_file, selected)
2502
-
2503
- assert_equal 'data.md',
2504
- @hd.instance_variable_get(:@delegate_object)[:filename]
2505
- assert_equal 'test.md||existing_history',
2506
- ENV.fetch(MDE_HISTORY_ENV_NAME, nil)
2507
- assert_includes @hd.instance_variable_get(:@run_state).link_history,
2508
- { block_name: 'selected_block',
2509
- filename: 'data.md' }
2510
- end
2511
- end
2512
-
2513
2312
  class TestHashDelegatorIterBlocksFromNestedFiles < Minitest::Test
2514
2313
  def setup
2515
2314
  @hd = HashDelegator.new
@@ -2544,7 +2343,7 @@ if $PROGRAM_NAME == __FILE__
2544
2343
  def setup
2545
2344
  @hd = HashDelegator.new
2546
2345
  @hd.stubs(:block_find).returns({})
2547
- @hd.stubs(:handle_opts_block)
2346
+ @hd.stubs(:update_options_and_trigger_reuse)
2548
2347
  end
2549
2348
 
2550
2349
  def test_load_auto_blocks_with_new_filename
@@ -2681,7 +2480,7 @@ if $PROGRAM_NAME == __FILE__
2681
2480
  result = @hd.yield_line_if_selected('Test line', [:line])
2682
2481
  assert_nil result
2683
2482
  end
2684
- end # class TestHashDelegator
2483
+ end
2685
2484
 
2686
2485
  class TestHashDelegator < Minitest::Test
2687
2486
  def setup