markdown_exec 1.8 → 1.8.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -145,10 +145,6 @@ module MarkdownExec
145
145
 
146
146
  private
147
147
 
148
- def should_add_back_option?
149
- @delegate_object[:menu_with_back] && history_env_state_exist?
150
- end
151
-
152
148
  def add_back_option(menu_blocks)
153
149
  append_chrome_block(menu_blocks, MenuState::BACK)
154
150
  end
@@ -216,42 +212,16 @@ module MarkdownExec
216
212
 
217
213
  # private
218
214
 
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.
215
+ # Applies shell color options to the given string if applicable.
245
216
  #
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)
217
+ # @param name [String] The name to potentially colorize.
218
+ # @param shell_color_option [Symbol, nil] The shell color option to apply.
219
+ # @return [String] The colorized or original name string.
220
+ def apply_shell_color_option(name, shell_color_option)
221
+ if shell_color_option && @delegate_object[shell_color_option].present?
222
+ string_send_color(name, shell_color_option)
253
223
  else
254
- handle_generic_block(mdoc, selected)
224
+ name
255
225
  end
256
226
  end
257
227
 
@@ -286,22 +256,6 @@ module MarkdownExec
286
256
 
287
257
  # private
288
258
 
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
259
  def cfile
306
260
  @cfile ||= CachedNestedFileReader.new(
307
261
  import_pattern: @delegate_object.fetch(:import_pattern) #, "^ *@import +(?<name>.+?) *$")
@@ -322,25 +276,6 @@ module MarkdownExec
322
276
  true
323
277
  end
324
278
 
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?
340
-
341
- exit @delegate_object[exception_sym]
342
- end
343
-
344
279
  # Collects required code lines based on the selected block and the delegate object's configuration.
345
280
  # If the block type is VARS, it also sets environment variables based on the block's content.
346
281
  #
@@ -348,7 +283,7 @@ module MarkdownExec
348
283
  # @param selected [Hash] The selected block.
349
284
  # @return [Array<String>] Required code blocks as an array of lines.
350
285
  def collect_required_code_lines(mdoc, selected)
351
- set_environment_variables(selected) if selected[:shell] == BlockType::VARS
286
+ set_environment_variables_for_block(selected) if selected[:shell] == BlockType::VARS
352
287
 
353
288
  required = mdoc.collect_recursively_required_code(
354
289
  @delegate_object[:block_name],
@@ -367,19 +302,6 @@ module MarkdownExec
367
302
  read_required_blocks_from_temp_file + required[:code]
368
303
  end
369
304
 
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
-
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
381
- end
382
-
383
305
  def command_execute(command, args: [])
384
306
  @run_state.files = Hash.new([])
385
307
  @run_state.options = @delegate_object
@@ -441,6 +363,23 @@ module MarkdownExec
441
363
  error_handler('command_or_user_selected_block')
442
364
  end
443
365
 
366
+ # This method is responsible for handling the execution of generic blocks in a markdown document.
367
+ # It collects the required code lines from the document and, depending on the configuration,
368
+ # may display the code for user approval before execution. It then executes the approved block.
369
+ #
370
+ # @param mdoc [Object] The markdown document object containing code blocks.
371
+ # @param selected [Hash] The selected item from the menu to be executed.
372
+ # @return [LoadFileNextBlock] An object indicating whether to load the next block or reuse the current one.
373
+ def compile_execute_bash_and_special_blocks_and_trigger_reuse(mdoc, selected)
374
+ required_lines = collect_required_code_lines(mdoc, selected)
375
+ output_or_approval = @delegate_object[:output_script] || @delegate_object[:user_must_approve]
376
+ display_required_code(required_lines) if output_or_approval
377
+ allow_execution = @delegate_object[:user_must_approve] ? prompt_for_user_approval(required_lines) : true
378
+ execute_approved_block(required_lines, selected) if allow_execution
379
+
380
+ LoadFileNextBlock.new(LoadFile::Reuse, '')
381
+ end
382
+
444
383
  def copy_to_clipboard(required_lines)
445
384
  text = required_lines.flatten.join($INPUT_RECORD_SEPARATOR)
446
385
  Clipboard.copy(text)
@@ -534,12 +473,17 @@ module MarkdownExec
534
473
  FileUtils.mkdir_p(File.dirname(file_path))
535
474
  end
536
475
 
537
- def write_file_content(file_path, content)
538
- File.write(file_path, content)
539
- end
476
+ def create_divider(position)
477
+ divider_key = position == :initial ? :menu_initial_divider : :menu_final_divider
478
+ oname = format(@delegate_object[:menu_divider_format],
479
+ safeval(@delegate_object[divider_key]))
540
480
 
541
- def set_file_permissions(file_path, chmod_value)
542
- File.chmod(chmod_value, file_path)
481
+ FCB.new(
482
+ chrome: true,
483
+ disabled: '',
484
+ dname: string_send_color(oname, :menu_divider_color),
485
+ oname: oname
486
+ )
543
487
  end
544
488
 
545
489
  # Creates a temporary file, writes the provided code blocks into it,
@@ -557,14 +501,6 @@ module MarkdownExec
557
501
  Dir::Tmpname.create(self.class.to_s) { |path| path }
558
502
  end
559
503
 
560
- def write_to_file(path, content)
561
- File.write(path, content)
562
- end
563
-
564
- def set_environment_variable(path)
565
- ENV['MDE_LINK_REQUIRED_FILE'] = path
566
- end
567
-
568
504
  # Updates the title of an FCB object from its body content if the title is nil or empty.
569
505
  def default_block_title_from_body(fcb)
570
506
  return unless fcb.title.nil? || fcb.title.empty?
@@ -594,16 +530,6 @@ module MarkdownExec
594
530
  error_handler('delete_required_temp_file')
595
531
  end
596
532
 
597
- # private
598
-
599
- def fetch_temp_blocks_file_path
600
- ENV.fetch('MDE_LINK_REQUIRED_FILE', nil)
601
- end
602
-
603
- def safely_remove_file(path)
604
- FileUtils.rm_f(path)
605
- end
606
-
607
533
  # Determines the state of a selected block in the menu based on the selected option.
608
534
  # It categorizes the selected option into either EXIT, BACK, or CONTINUE state.
609
535
  #
@@ -635,12 +561,9 @@ module MarkdownExec
635
561
  :script_preview_frame_color)
636
562
  end
637
563
 
638
- # private
639
-
640
- def output_color_formatted(data_sym, color_sym)
641
- formatted_string = string_send_color(@delegate_object[data_sym],
642
- color_sym)
643
- @fout.fout formatted_string
564
+ def divider_formatting_present?(position)
565
+ divider_key = position == :initial ? :menu_initial_divider : :menu_final_divider
566
+ @delegate_object[:menu_divider_format].present? && @delegate_object[divider_key].present?
644
567
  end
645
568
 
646
569
  def error_handler(name = '', opts = {})
@@ -665,29 +588,25 @@ module MarkdownExec
665
588
  post_execution_process
666
589
  end
667
590
 
668
- # private
669
-
670
- def set_script_block_name(selected)
671
- @run_state.script_block_name = selected[:oname]
672
- end
673
-
674
- def write_command_file_if_needed(lines)
675
- write_command_file(lines) if @delegate_object[:save_executed_script]
676
- end
677
-
678
- def format_and_execute_command(lines)
679
- formatted_command = lines.flatten.join("\n")
680
- @fout.fout fetch_color(data_sym: :script_execution_head,
681
- color_sym: :script_execution_frame_color)
682
- command_execute(formatted_command, args: @pass_args)
683
- @fout.fout fetch_color(data_sym: :script_execution_tail,
684
- color_sym: :script_execution_frame_color)
685
- end
686
-
687
- def post_execution_process
688
- initialize_and_save_execution_output
689
- output_execution_summary
690
- output_execution_result
591
+ # Execute a code block after approval and provide user interaction options.
592
+ #
593
+ # This method displays required code blocks, asks for user approval, and
594
+ # executes the code block if approved. It also allows users to copy the
595
+ # code to the clipboard or save it to a file.
596
+ #
597
+ # @param opts [Hash] Options hash containing configuration settings.
598
+ # @param mdoc [YourMDocClass] An instance of the MDoc class.
599
+ #
600
+ def execute_bash_and_special_blocks(selected, mdoc)
601
+ if selected.fetch(:shell, '') == BlockType::LINK
602
+ push_link_history_and_trigger_load(selected.fetch(:body, ''), mdoc, selected)
603
+ elsif @menu_user_clicked_back_link
604
+ pop_link_history_and_trigger_load
605
+ elsif selected[:shell] == BlockType::OPTS
606
+ update_options_and_trigger_reuse(selected, @menu_base_options)
607
+ else
608
+ compile_execute_bash_and_special_blocks_and_trigger_reuse(mdoc, selected)
609
+ end
691
610
  end
692
611
 
693
612
  # Retrieves a specific data symbol from the delegate object, converts it to a string,
@@ -704,6 +623,31 @@ module MarkdownExec
704
623
  string_send_color(data_string, color_sym)
705
624
  end
706
625
 
626
+ def fetch_temp_blocks_file_path
627
+ ENV.fetch('MDE_LINK_REQUIRED_FILE', nil)
628
+ end
629
+
630
+ # DebugHelper.d ["HDmm method_name: #{method_name}", "#{first_n_caller_items 1}"]
631
+ def first_n_caller_items(n)
632
+ # Get the call stack
633
+ call_stack = caller
634
+ base_path = File.realpath('.')
635
+
636
+ # Modify the call stack to remove the base path and keep only the first n items
637
+ call_stack.take(n + 1)[1..].map do |line|
638
+ " . #{line.sub(/^#{Regexp.escape(base_path)}\//, '')}"
639
+ end.join("\n")
640
+ end
641
+
642
+ def format_and_execute_command(lines)
643
+ formatted_command = lines.flatten.join("\n")
644
+ @fout.fout fetch_color(data_sym: :script_execution_head,
645
+ color_sym: :script_execution_frame_color)
646
+ command_execute(formatted_command, args: @pass_args)
647
+ @fout.fout fetch_color(data_sym: :script_execution_tail,
648
+ color_sym: :script_execution_frame_color)
649
+ end
650
+
707
651
  # Formats a string based on a given context and applies color styling to it.
708
652
  # It retrieves format and color information from the delegate object and processes accordingly.
709
653
  #
@@ -744,131 +688,29 @@ module MarkdownExec
744
688
  @delegate_object[:block_name_match])
745
689
 
746
690
  fcb.stdin = extract_named_captures_from_option(titlexcall,
747
- @delegate_object[:block_stdin_scan])
748
- fcb.stdout = extract_named_captures_from_option(titlexcall,
749
- @delegate_object[:block_stdout_scan])
750
-
751
- shell_color_option = SHELL_COLOR_OPTIONS[fcb[:shell]]
752
- fcb.title = fcb.oname = bm && bm[1] ? bm[:title] : titlexcall
753
- fcb.dname = apply_shell_color_option(fcb.oname, shell_color_option)
754
-
755
- fcb
756
- end
757
-
758
- # private
759
-
760
- # Applies shell color options to the given string if applicable.
761
- #
762
- # @param name [String] The name to potentially colorize.
763
- # @param shell_color_option [Symbol, nil] The shell color option to apply.
764
- # @return [String] The colorized or original name string.
765
- def apply_shell_color_option(name, shell_color_option)
766
- if shell_color_option && @delegate_object[shell_color_option].present?
767
- string_send_color(name, shell_color_option)
768
- else
769
- name
770
- end
771
- end
772
-
773
- # This method handles the back-link operation in the Markdown execution context.
774
- # It updates the history state and prepares to load the next block.
775
- #
776
- # @return [LoadFileNextBlock] An object indicating the action to load the next block.
777
- def handle_back_link
778
- history_state_pop
779
- LoadFileNextBlock.new(LoadFile::Load, '')
780
- end
781
-
782
- # private
783
- # Updates the delegate object's state based on the provided block state.
784
- # It sets the block name and determines if the user clicked the back link in the menu.
785
- #
786
- # @param block_state [Object] An object representing the state of a block in the menu.
787
- def handle_block_state(block_state)
788
- unless [MenuState::BACK,
789
- MenuState::CONTINUE].include?(block_state.state)
790
- return
791
- end
792
-
793
- @delegate_object[:block_name] = block_state.block[:dname]
794
- @menu_user_clicked_back_link = block_state.state == MenuState::BACK
795
- end
796
-
797
- # This method is responsible for handling the execution of generic blocks in a markdown document.
798
- # It collects the required code lines from the document and, depending on the configuration,
799
- # may display the code for user approval before execution. It then executes the approved block.
800
- #
801
- # @param mdoc [Object] The markdown document object containing code blocks.
802
- # @param selected [Hash] The selected item from the menu to be executed.
803
- # @return [LoadFileNextBlock] An object indicating whether to load the next block or reuse the current one.
804
- def handle_generic_block(mdoc, selected)
805
- required_lines = collect_required_code_lines(mdoc, selected)
806
- output_or_approval = @delegate_object[:output_script] || @delegate_object[:user_must_approve]
807
- display_required_code(required_lines) if output_or_approval
808
- allow_execution = @delegate_object[:user_must_approve] ? prompt_for_user_approval(required_lines) : true
809
- execute_approved_block(required_lines, selected) if allow_execution
810
-
811
- LoadFileNextBlock.new(LoadFile::Reuse, '')
812
- end
813
-
814
- # Handles the processing of a link block in Markdown Execution.
815
- # It loads YAML data from the body content, pushes the state to history,
816
- # sets environment variables, and decides on the next block to load.
817
- #
818
- # @param body [Array<String>] The body content as an array of strings.
819
- # @param mdoc [Object] Markdown document object.
820
- # @param selected [Boolean] Selected state.
821
- # @return [LoadFileNextBlock] Object indicating the next action for file loading.
822
- def handle_link_block(body, mdoc, selected)
823
- data = parse_yaml_data_from_body(body)
824
- data_file = data['file']
825
- return LoadFileNextBlock.new(LoadFile::Reuse, '') unless data_file
826
-
827
- history_state_push(mdoc, data_file, selected)
828
- set_environment_variables(data['vars'])
829
-
830
- LoadFileNextBlock.new(LoadFile::Load, data['block'] || '')
831
- end
832
-
833
- # private
834
-
835
- def parse_yaml_data_from_body(body)
836
- body.any? ? YAML.load(body.join("\n")) : {}
837
- end
838
-
839
- def set_environment_variables(vars)
840
- vars ||= []
841
- vars.each { |key, value| ENV[key] = value.to_s }
842
- end
843
-
844
- # Processes YAML data from the selected menu item, updating delegate objects and optionally printing formatted output.
845
- # @param selected [Hash] Selected item from the menu containing a YAML body.
846
- # @param tgt2 [Hash, nil] An optional target hash to update with YAML data.
847
- # @return [LoadFileNextBlock] An instance indicating the next action for loading files.
848
- def handle_opts_block(selected, tgt2 = nil)
849
- data = YAML.load(selected[:body].join("\n"))
850
- (data || []).each do |key, value|
851
- update_delegate_and_target(key, value, tgt2)
852
- if @delegate_object[:menu_opts_set_format].present?
853
- print_formatted_option(key,
854
- value)
855
- end
856
- end
857
- LoadFileNextBlock.new(LoadFile::Reuse, '')
858
- end
691
+ @delegate_object[:block_stdin_scan])
692
+ fcb.stdout = extract_named_captures_from_option(titlexcall,
693
+ @delegate_object[:block_stdout_scan])
859
694
 
860
- # private
695
+ shell_color_option = SHELL_COLOR_OPTIONS[fcb[:shell]]
696
+ fcb.title = fcb.oname = bm && bm[1] ? bm[:title] : titlexcall
697
+ fcb.dname = apply_shell_color_option(fcb.oname, shell_color_option)
861
698
 
862
- def update_delegate_and_target(key, value, tgt2)
863
- sym_key = key.to_sym
864
- @delegate_object[sym_key] = value
865
- tgt2[sym_key] = value if tgt2
699
+ fcb
866
700
  end
867
701
 
868
- def print_formatted_option(key, value)
869
- formatted_str = format(@delegate_object[:menu_opts_set_format],
870
- { key: key, value: value })
871
- print string_send_color(formatted_str, :menu_opts_set_color)
702
+ # Updates the delegate object's state based on the provided block state.
703
+ # It sets the block name and determines if the user clicked the back link in the menu.
704
+ #
705
+ # @param block_state [Object] An object representing the state of a block in the menu.
706
+ def handle_block_state(block_state)
707
+ unless [MenuState::BACK,
708
+ MenuState::CONTINUE].include?(block_state.state)
709
+ return
710
+ end
711
+
712
+ @delegate_object[:block_name] = block_state.block[:oname]
713
+ @menu_user_clicked_back_link = block_state.state == MenuState::BACK
872
714
  end
873
715
 
874
716
  def handle_stream(stream, file_type, swap: false)
@@ -893,10 +735,10 @@ module MarkdownExec
893
735
  end
894
736
  end
895
737
 
896
- def wait_for_stream_processing
897
- @process_mutex.synchronize do
898
- @process_cv.wait(@process_mutex)
899
- end
738
+ # Checks if a history environment variable is set and returns its value if present.
739
+ # @return [String, nil] The value of the history environment variable or nil if not present.
740
+ def history_env_state_exist?
741
+ ENV.fetch(MDE_HISTORY_ENV_NAME, '').present?
900
742
  end
901
743
 
902
744
  # Partitions the history state from the environment variable based on the document separator.
@@ -949,10 +791,6 @@ module MarkdownExec
949
791
  body.lines.map { |line| indent + line.chomp }.join("\n")
950
792
  end
951
793
 
952
- def initialize_fcb_names(fcb)
953
- fcb.oname = fcb.dname = fcb.title || ''
954
- end
955
-
956
794
  # Initializes variables for regex and other states
957
795
  def initial_state
958
796
  {
@@ -986,6 +824,10 @@ module MarkdownExec
986
824
  write_execution_output_to_file
987
825
  end
988
826
 
827
+ def initialize_fcb_names(fcb)
828
+ fcb.oname = fcb.dname = fcb.title || ''
829
+ end
830
+
989
831
  # Iterates through blocks in a file, applying the provided block to each line.
990
832
  # The iteration only occurs if the file exists.
991
833
  # @yield [Symbol] :filter Yields to obtain selected messages for processing.
@@ -1016,46 +858,11 @@ module MarkdownExec
1016
858
  block = block_find(all_blocks, :oname, block_name)
1017
859
  return unless block
1018
860
 
1019
- handle_opts_block(block, @delegate_object)
861
+ update_options_and_trigger_reuse(block, @delegate_object)
1020
862
  @most_recent_loaded_filename = @delegate_object[:filename]
1021
863
  true
1022
864
  end
1023
865
 
1024
- # DebugHelper.d ["HDmm method_name: #{method_name}", "#{first_n_caller_items 1}"]
1025
- def first_n_caller_items(n)
1026
- # Get the call stack
1027
- call_stack = caller
1028
- base_path = File.realpath('.')
1029
-
1030
- # Modify the call stack to remove the base path and keep only the first n items
1031
- call_stack.take(n + 1)[1..].map do |line|
1032
- " . #{line.sub(/^#{Regexp.escape(base_path)}\//, '')}"
1033
- end.join("\n")
1034
- end
1035
-
1036
- # Checks if a history environment variable is set and returns its value if present.
1037
- # @return [String, nil] The value of the history environment variable or nil if not present.
1038
- def history_env_state_exist?
1039
- ENV.fetch(MDE_HISTORY_ENV_NAME, '').present?
1040
- end
1041
-
1042
- # def history_env_state_exist?
1043
- # history = ENV.fetch(MDE_HISTORY_ENV_NAME, '')
1044
- # history.present? ? history : nil
1045
- # end
1046
-
1047
- # If a method is missing, treat it as a key for the @delegate_object.
1048
- def method_missing(method_name, *args, &block)
1049
- if @delegate_object.respond_to?(method_name)
1050
- @delegate_object.send(method_name, *args, &block)
1051
- elsif method_name.to_s.end_with?('=') && args.size == 1
1052
- @delegate_object[method_name.to_s.chop.to_sym] = args.first
1053
- else
1054
- @delegate_object[method_name]
1055
- # super
1056
- end
1057
- end
1058
-
1059
866
  def mdoc_and_blocks_from_nested_files
1060
867
  menu_blocks = blocks_from_nested_files
1061
868
  mdoc = MDoc.new(menu_blocks) do |nopts|
@@ -1071,9 +878,7 @@ module MarkdownExec
1071
878
 
1072
879
  # recreate menu with new options
1073
880
  #
1074
- if load_auto_blocks(all_blocks)
1075
- all_blocks, mdoc = mdoc_and_blocks_from_nested_files
1076
- end
881
+ all_blocks, mdoc = mdoc_and_blocks_from_nested_files if load_auto_blocks(all_blocks)
1077
882
 
1078
883
  menu_blocks = mdoc.fcbs_per_options(@delegate_object)
1079
884
  add_menu_chrome_blocks!(menu_blocks)
@@ -1105,6 +910,18 @@ module MarkdownExec
1105
910
  end
1106
911
  end
1107
912
 
913
+ # If a method is missing, treat it as a key for the @delegate_object.
914
+ def method_missing(method_name, *args, &block)
915
+ if @delegate_object.respond_to?(method_name)
916
+ @delegate_object.send(method_name, *args, &block)
917
+ elsif method_name.to_s.end_with?('=') && args.size == 1
918
+ @delegate_object[method_name.to_s.chop.to_sym] = args.first
919
+ else
920
+ @delegate_object[method_name]
921
+ # super
922
+ end
923
+ end
924
+
1108
925
  def next_block_name_from_command_line_arguments
1109
926
  return MenuControl::Repeat unless @delegate_object[:input_cli_rest].present?
1110
927
 
@@ -1112,6 +929,14 @@ module MarkdownExec
1112
929
  MenuControl::Fresh
1113
930
  end
1114
931
 
932
+ # private
933
+
934
+ def output_color_formatted(data_sym, color_sym)
935
+ formatted_string = string_send_color(@delegate_object[data_sym],
936
+ color_sym)
937
+ @fout.fout formatted_string
938
+ end
939
+
1115
940
  def output_execution_result
1116
941
  @fout.fout fetch_color(data_sym: :execution_report_preview_head,
1117
942
  color_sym: :execution_report_preview_frame_color)
@@ -1158,6 +983,27 @@ module MarkdownExec
1158
983
  ), level: level
1159
984
  end
1160
985
 
986
+ # private
987
+
988
+ def parse_yaml_data_from_body(body)
989
+ body.any? ? YAML.load(body.join("\n")) : {}
990
+ end
991
+
992
+ # This method handles the back-link operation in the Markdown execution context.
993
+ # It updates the history state and prepares to load the next block.
994
+ #
995
+ # @return [LoadFileNextBlock] An object indicating the action to load the next block.
996
+ def pop_link_history_and_trigger_load
997
+ history_state_pop
998
+ LoadFileNextBlock.new(LoadFile::Load, '')
999
+ end
1000
+
1001
+ def post_execution_process
1002
+ initialize_and_save_execution_output
1003
+ output_execution_summary
1004
+ output_execution_result
1005
+ end
1006
+
1161
1007
  # Prepare the blocks menu by adding labels and other necessary details.
1162
1008
  #
1163
1009
  # @param all_blocks [Array<Hash>] The list of blocks from the file.
@@ -1185,6 +1031,28 @@ module MarkdownExec
1185
1031
  end.compact
1186
1032
  end
1187
1033
 
1034
+ def print_formatted_option(key, value)
1035
+ formatted_str = format(@delegate_object[:menu_opts_set_format],
1036
+ { key: key, value: value })
1037
+ print string_send_color(formatted_str, :menu_opts_set_color)
1038
+ end
1039
+
1040
+ # private
1041
+
1042
+ def process_block_based_on_type(blocks, btype, fcb)
1043
+ case btype
1044
+ when :blocks
1045
+ blocks.push(get_block_summary(fcb))
1046
+ when :filter
1047
+ %i[blocks line]
1048
+ when :line
1049
+ unless @delegate_object[:no_chrome]
1050
+ create_and_add_chrome_blocks(blocks,
1051
+ fcb)
1052
+ end
1053
+ end
1054
+ end
1055
+
1188
1056
  ##
1189
1057
  # Presents a menu to the user for approving an action and performs additional tasks based on the selection.
1190
1058
  # The function provides options for approval, rejection, copying data to clipboard, or saving data to a file.
@@ -1243,13 +1111,27 @@ module MarkdownExec
1243
1111
  exit 1
1244
1112
  end
1245
1113
 
1246
- def save_to_file(required_lines)
1247
- write_command_file(required_lines)
1248
- @fout.fout "File saved: #{@run_state.saved_filespec}"
1249
- end
1250
-
1251
1114
  # public
1252
1115
 
1116
+ # Handles the processing of a link block in Markdown Execution.
1117
+ # It loads YAML data from the body content, pushes the state to history,
1118
+ # sets environment variables, and decides on the next block to load.
1119
+ #
1120
+ # @param body [Array<String>] The body content as an array of strings.
1121
+ # @param mdoc [Object] Markdown document object.
1122
+ # @param selected [Boolean] Selected state.
1123
+ # @return [LoadFileNextBlock] Object indicating the next action for file loading.
1124
+ def push_link_history_and_trigger_load(body, mdoc, selected)
1125
+ data = parse_yaml_data_from_body(body)
1126
+ data_file = data['file']
1127
+ return LoadFileNextBlock.new(LoadFile::Reuse, '') unless data_file
1128
+
1129
+ history_state_push(mdoc, data_file, selected)
1130
+ set_environment_variables_per_array(data['vars'])
1131
+
1132
+ LoadFileNextBlock.new(LoadFile::Load, data['block'] || '')
1133
+ end
1134
+
1253
1135
  # Reads required code blocks from a temporary file specified by an environment variable.
1254
1136
  # @return [Array<String>] Lines read from the temporary file, or an empty array if file is not found or path is empty.
1255
1137
  def read_required_blocks_from_temp_file
@@ -1265,6 +1147,29 @@ module MarkdownExec
1265
1147
  end
1266
1148
  end
1267
1149
 
1150
+ def runtime_exception(exception_sym, name, items)
1151
+ if @delegate_object[exception_sym] != 0
1152
+ data = { name: name, detail: items.join(', ') }
1153
+ warn(
1154
+ format(
1155
+ @delegate_object.fetch(:exception_format_name, "\n%{name}"),
1156
+ data
1157
+ ).send(@delegate_object.fetch(:exception_color_name, :red)) +
1158
+ format(
1159
+ @delegate_object.fetch(:exception_format_detail, " - %{detail}\n"),
1160
+ data
1161
+ ).send(@delegate_object.fetch(:exception_color_detail, :yellow))
1162
+ )
1163
+ end
1164
+ return unless (@delegate_object[exception_sym]).positive?
1165
+
1166
+ exit @delegate_object[exception_sym]
1167
+ end
1168
+
1169
+ def safely_remove_file(path)
1170
+ FileUtils.rm_f(path)
1171
+ end
1172
+
1268
1173
  # Evaluates the given string as Ruby code and rescues any StandardErrors.
1269
1174
  # If an error occurs, it calls the error_handler method with 'safeval'.
1270
1175
  # @param str [String] The string to be evaluated.
@@ -1281,17 +1186,22 @@ module MarkdownExec
1281
1186
  # error_handler('safeval')
1282
1187
  # end
1283
1188
 
1189
+ def save_to_file(required_lines)
1190
+ write_command_file(required_lines)
1191
+ @fout.fout "File saved: #{@run_state.saved_filespec}"
1192
+ end
1193
+
1284
1194
  # Select and execute a code block from a Markdown document.
1285
1195
  #
1286
1196
  # This method allows the user to interactively select a code block from a
1287
1197
  # Markdown document, obtain approval, and execute the chosen block of code.
1288
1198
  #
1289
1199
  # @return [Nil] Returns nil if no code block is selected or an error occurs.
1290
- def select_approve_and_execute_block(_execute: true)
1200
+ def select_execute_bash_and_special_blocks(_execute: true)
1291
1201
  @menu_base_options = @delegate_object
1292
1202
  repeat_menu = @menu_base_options[:block_name].present? ? MenuControl::Fresh : MenuControl::Repeat
1293
1203
  load_file_next_block = LoadFileNextBlock.new(LoadFile::Reuse)
1294
- default = nil
1204
+ menu_default_dname = nil
1295
1205
 
1296
1206
  @menu_state_filename = @menu_base_options[:filename]
1297
1207
  @menu_state_block_name = @menu_base_options[:block_name]
@@ -1319,11 +1229,11 @@ module MarkdownExec
1319
1229
  )
1320
1230
  end
1321
1231
  block_state = command_or_user_selected_block(blocks_in_file,
1322
- menu_blocks, default)
1232
+ menu_blocks, menu_default_dname)
1323
1233
  return if block_state.state == MenuState::EXIT
1324
1234
 
1325
1235
  if block_state.block.nil?
1326
- warn_format('select_approve_and_execute_block', "Block not found -- #{@delegate_object[:block_name]}",
1236
+ warn_format('select_execute_bash_and_special_blocks', "Block not found -- #{@delegate_object[:block_name]}",
1327
1237
  { abort: true })
1328
1238
  # error_handler("Block not found -- #{opts[:block_name]}", { abort: true })
1329
1239
  end
@@ -1332,9 +1242,13 @@ module MarkdownExec
1332
1242
  warn block_state.block.to_yaml.sub(/^(?:---\n)?/, "Block:\n")
1333
1243
  end
1334
1244
 
1335
- load_file_next_block = approve_and_execute_block(block_state.block,
1336
- mdoc)
1337
- default = load_file_next_block.load_file == LoadFile::Load ? nil : @delegate_object[:block_name]
1245
+ load_file_next_block = execute_bash_and_special_blocks(block_state.block,
1246
+ mdoc)
1247
+ # if the same menu is being displayed,
1248
+ # collect the display name of the selected menu item
1249
+ # for use as the default item
1250
+ menu_default_dname = load_file_next_block.load_file == LoadFile::Load ? nil : block_state.block[:dname]
1251
+
1338
1252
  @menu_base_options[:block_name] =
1339
1253
  @delegate_object[:block_name] = load_file_next_block.next_block
1340
1254
  @menu_base_options[:filename] = @delegate_object[:filename]
@@ -1362,7 +1276,7 @@ module MarkdownExec
1362
1276
  @menu_state_block_name = @menu_base_options[:block_name]
1363
1277
  end
1364
1278
  rescue StandardError
1365
- error_handler('select_approve_and_execute_block',
1279
+ error_handler('select_execute_bash_and_special_blocks',
1366
1280
  { abort: true })
1367
1281
  end
1368
1282
 
@@ -1393,6 +1307,38 @@ module MarkdownExec
1393
1307
  error_handler('select_option_with_metadata')
1394
1308
  end
1395
1309
 
1310
+ def set_environment_variable(path)
1311
+ ENV['MDE_LINK_REQUIRED_FILE'] = path
1312
+ end
1313
+
1314
+ def set_environment_variables_for_block(selected)
1315
+ YAML.load(selected[:body].join("\n")).each do |key, value|
1316
+ ENV[key] = value.to_s
1317
+ next unless @delegate_object[:menu_vars_set_format].present?
1318
+
1319
+ formatted_string = format(@delegate_object[:menu_vars_set_format],
1320
+ { key: key, value: value })
1321
+ print string_send_color(formatted_string, :menu_vars_set_color)
1322
+ end
1323
+ end
1324
+
1325
+ def set_environment_variables_per_array(vars)
1326
+ vars ||= []
1327
+ vars.each { |key, value| ENV[key] = value.to_s }
1328
+ end
1329
+
1330
+ def set_file_permissions(file_path, chmod_value)
1331
+ File.chmod(chmod_value, file_path)
1332
+ end
1333
+
1334
+ def set_script_block_name(selected)
1335
+ @run_state.script_block_name = selected[:oname]
1336
+ end
1337
+
1338
+ def should_add_back_option?
1339
+ @delegate_object[:menu_with_back] && history_env_state_exist?
1340
+ end
1341
+
1396
1342
  # Initializes a new fenced code block (FCB) object based on the provided line and heading information.
1397
1343
  # @param line [String] The line initiating the fenced block.
1398
1344
  # @param headings [Array<String>] Current headings hierarchy.
@@ -1451,6 +1397,13 @@ module MarkdownExec
1451
1397
  symbols: { cross: ' ' }
1452
1398
  )
1453
1399
  end
1400
+ # private
1401
+
1402
+ def update_delegate_and_target(key, value, tgt2)
1403
+ sym_key = key.to_sym
1404
+ @delegate_object[sym_key] = value
1405
+ tgt2[sym_key] = value if tgt2
1406
+ end
1454
1407
 
1455
1408
  # Updates the hierarchy of document headings based on the given line.
1456
1409
  # Utilizes regular expressions to identify heading levels.
@@ -1548,6 +1501,25 @@ module MarkdownExec
1548
1501
  yield_to_block_if_applicable(fcb, selected_messages, &block)
1549
1502
  end
1550
1503
 
1504
+ # Processes YAML data from the selected menu item, updating delegate objects and optionally printing formatted output.
1505
+ # @param selected [Hash] Selected item from the menu containing a YAML body.
1506
+ # @param tgt2 [Hash, nil] An optional target hash to update with YAML data.
1507
+ # @return [LoadFileNextBlock] An instance indicating the next action for loading files.
1508
+ def update_options_and_trigger_reuse(selected, tgt2 = nil)
1509
+ data = YAML.load(selected[:body].join("\n"))
1510
+ (data || []).each do |key, value|
1511
+ update_delegate_and_target(key, value, tgt2)
1512
+ print_formatted_option(key, value) if @delegate_object[:menu_opts_set_format].present?
1513
+ end
1514
+ LoadFileNextBlock.new(LoadFile::Reuse, '')
1515
+ end
1516
+
1517
+ def wait_for_stream_processing
1518
+ @process_mutex.synchronize do
1519
+ @process_cv.wait(@process_mutex)
1520
+ end
1521
+ end
1522
+
1551
1523
  def wait_for_user_selected_block(all_blocks, menu_blocks, default)
1552
1524
  block_state = wait_for_user_selection(all_blocks, menu_blocks, default)
1553
1525
  handle_block_state(block_state)
@@ -1589,7 +1561,6 @@ module MarkdownExec
1589
1561
  filename: @delegate_object[:filename],
1590
1562
  prefix: @delegate_object[:saved_script_filename_prefix],
1591
1563
  time: time_now)
1592
-
1593
1564
  @run_state.saved_filespec =
1594
1565
  File.join(@delegate_object[:saved_script_folder],
1595
1566
  @run_state.saved_script_filename)
@@ -1615,6 +1586,10 @@ module MarkdownExec
1615
1586
  error_handler('write_command_file')
1616
1587
  end
1617
1588
 
1589
+ def write_command_file_if_needed(lines)
1590
+ write_command_file(lines) if @delegate_object[:save_executed_script]
1591
+ end
1592
+
1618
1593
  def write_execution_output_to_file
1619
1594
  FileUtils.mkdir_p File.dirname(@delegate_object[:logged_stdout_filespec])
1620
1595
 
@@ -1630,6 +1605,10 @@ module MarkdownExec
1630
1605
  )
1631
1606
  end
1632
1607
 
1608
+ def write_file_content(file_path, content)
1609
+ File.write(file_path, content)
1610
+ end
1611
+
1633
1612
  # Writes required code blocks to a temporary file and sets an environment variable with its path.
1634
1613
  #
1635
1614
  # @param mdoc [Object] The Markdown document object.
@@ -1651,6 +1630,10 @@ module MarkdownExec
1651
1630
  create_temp_file_with_code(code_blocks)
1652
1631
  end
1653
1632
 
1633
+ def write_to_file(path, content)
1634
+ File.write(path, content)
1635
+ end
1636
+
1654
1637
  # Yields a line as a new block if the selected message type includes :line.
1655
1638
  # @param [String] line The line to be processed.
1656
1639
  # @param [Array<Symbol>] selected_messages A list of message types to check.
@@ -1713,26 +1696,26 @@ if $PROGRAM_NAME == __FILE__
1713
1696
  end
1714
1697
 
1715
1698
  # Test case for empty body
1716
- def test_handle_link_block_with_empty_body
1699
+ def test_push_link_history_and_trigger_load_with_empty_body
1717
1700
  assert_equal LoadFileNextBlock.new(LoadFile::Reuse, ''),
1718
- @hd.handle_link_block([], nil, false)
1701
+ @hd.push_link_history_and_trigger_load([], nil, false)
1719
1702
  end
1720
1703
 
1721
1704
  # Test case for non-empty body without 'file' key
1722
- def test_handle_link_block_without_file_key
1705
+ def test_push_link_history_and_trigger_load_without_file_key
1723
1706
  body = ["vars:\n KEY: VALUE"]
1724
1707
  assert_equal LoadFileNextBlock.new(LoadFile::Reuse, ''),
1725
- @hd.handle_link_block(body, nil, false)
1708
+ @hd.push_link_history_and_trigger_load(body, nil, false)
1726
1709
  end
1727
1710
 
1728
1711
  # Test case for non-empty body with 'file' key
1729
- def test_handle_link_block_with_file_key
1712
+ def test_push_link_history_and_trigger_load_with_file_key
1730
1713
  body = ["file: sample_file\nblock: sample_block\nvars:\n KEY: VALUE"]
1731
1714
  expected_result = LoadFileNextBlock.new(LoadFile::Load,
1732
1715
  'sample_block')
1733
1716
  # mdoc = MDoc.new()
1734
1717
  assert_equal expected_result,
1735
- @hd.handle_link_block(body, nil, FCB.new)
1718
+ @hd.push_link_history_and_trigger_load(body, nil, FCB.new)
1736
1719
  end
1737
1720
 
1738
1721
  def test_history_env_state_exist_with_value
@@ -2254,11 +2237,11 @@ if $PROGRAM_NAME == __FILE__
2254
2237
  @hd.stubs(:history_state_pop)
2255
2238
  end
2256
2239
 
2257
- def test_handle_back_link
2240
+ def test_pop_link_history_and_trigger_load
2258
2241
  # Verifying that history_state_pop is called
2259
2242
  @hd.expects(:history_state_pop).once
2260
2243
 
2261
- result = @hd.handle_back_link
2244
+ result = @hd.pop_link_history_and_trigger_load
2262
2245
 
2263
2246
  # Asserting the result is an instance of LoadFileNextBlock
2264
2247
  assert_instance_of LoadFileNextBlock, result
@@ -2275,7 +2258,7 @@ if $PROGRAM_NAME == __FILE__
2275
2258
 
2276
2259
  def test_handle_block_state_with_back
2277
2260
  @mock_block_state.stubs(:state).returns(MenuState::BACK)
2278
- @mock_block_state.stubs(:block).returns({ dname: 'sample_block' })
2261
+ @mock_block_state.stubs(:block).returns({ oname: 'sample_block' })
2279
2262
 
2280
2263
  @hd.handle_block_state(@mock_block_state)
2281
2264
 
@@ -2286,7 +2269,7 @@ if $PROGRAM_NAME == __FILE__
2286
2269
 
2287
2270
  def test_handle_block_state_with_continue
2288
2271
  @mock_block_state.stubs(:state).returns(MenuState::CONTINUE)
2289
- @mock_block_state.stubs(:block).returns({ dname: 'another_block' })
2272
+ @mock_block_state.stubs(:block).returns({ oname: 'another_block' })
2290
2273
 
2291
2274
  @hd.handle_block_state(@mock_block_state)
2292
2275
 
@@ -2297,7 +2280,7 @@ if $PROGRAM_NAME == __FILE__
2297
2280
 
2298
2281
  def test_handle_block_state_with_other
2299
2282
  @mock_block_state.stubs(:state).returns(nil) # MenuState::OTHER
2300
- @mock_block_state.stubs(:block).returns({ dname: 'other_block' })
2283
+ @mock_block_state.stubs(:block).returns({ oname: 'other_block' })
2301
2284
 
2302
2285
  @hd.handle_block_state(@mock_block_state)
2303
2286
 
@@ -2313,7 +2296,7 @@ if $PROGRAM_NAME == __FILE__
2313
2296
  @selected_item = mock('FCB')
2314
2297
  end
2315
2298
 
2316
- def test_handle_generic_block_without_user_approval
2299
+ def test_compile_execute_bash_and_special_blocks_and_trigger_reuse_without_user_approval
2317
2300
  # Mock the delegate object configuration
2318
2301
  @hd.instance_variable_set(:@delegate_object,
2319
2302
  { output_script: false,
@@ -2323,7 +2306,7 @@ if $PROGRAM_NAME == __FILE__
2323
2306
  # Expectations and assertions go here
2324
2307
  end
2325
2308
 
2326
- def test_handle_generic_block_with_user_approval
2309
+ def test_compile_execute_bash_and_special_blocks_and_trigger_reuse_with_user_approval
2327
2310
  # Mock the delegate object configuration
2328
2311
  @hd.instance_variable_set(:@delegate_object,
2329
2312
  { output_script: false,
@@ -2333,7 +2316,7 @@ if $PROGRAM_NAME == __FILE__
2333
2316
  # Expectations and assertions go here
2334
2317
  end
2335
2318
 
2336
- def test_handle_generic_block_with_output_script
2319
+ def test_compile_execute_bash_and_special_blocks_and_trigger_reuse_with_output_script
2337
2320
  # Mock the delegate object configuration
2338
2321
  @hd.instance_variable_set(:@delegate_object,
2339
2322
  { output_script: true,
@@ -2354,11 +2337,11 @@ if $PROGRAM_NAME == __FILE__
2354
2337
  @hd.stubs(:print)
2355
2338
  end
2356
2339
 
2357
- def test_handle_opts_block
2340
+ def test_update_options_and_trigger_reuse
2358
2341
  selected = { body: ['option1: value1'] }
2359
2342
  tgt2 = {}
2360
2343
 
2361
- result = @hd.handle_opts_block(selected, tgt2)
2344
+ result = @hd.update_options_and_trigger_reuse(selected, tgt2)
2362
2345
 
2363
2346
  assert_instance_of LoadFileNextBlock, result
2364
2347
  assert_equal 'value1',
@@ -2366,11 +2349,11 @@ if $PROGRAM_NAME == __FILE__
2366
2349
  assert_equal 'value1', tgt2[:option1]
2367
2350
  end
2368
2351
 
2369
- def test_handle_opts_block_without_format
2352
+ def test_update_options_and_trigger_reuse_without_format
2370
2353
  selected = { body: ['option2: value2'] }
2371
2354
  @hd.instance_variable_set(:@delegate_object, {})
2372
2355
 
2373
- result = @hd.handle_opts_block(selected)
2356
+ result = @hd.update_options_and_trigger_reuse(selected)
2374
2357
 
2375
2358
  assert_instance_of LoadFileNextBlock, result
2376
2359
  assert_equal 'value2',
@@ -2537,7 +2520,7 @@ if $PROGRAM_NAME == __FILE__
2537
2520
  def setup
2538
2521
  @hd = HashDelegator.new
2539
2522
  @hd.stubs(:block_find).returns({})
2540
- @hd.stubs(:handle_opts_block)
2523
+ @hd.stubs(:update_options_and_trigger_reuse)
2541
2524
  end
2542
2525
 
2543
2526
  def test_load_auto_blocks_with_new_filename
@@ -2732,7 +2715,7 @@ if $PROGRAM_NAME == __FILE__
2732
2715
 
2733
2716
  def test_wait_for_user_selected_block_with_back_state
2734
2717
  mock_block_state = Struct.new(:state, :block).new(MenuState::BACK,
2735
- { dname: 'back_block' })
2718
+ { oname: 'back_block' })
2736
2719
  @hd.stubs(:wait_for_user_selection).returns(mock_block_state)
2737
2720
 
2738
2721
  result = @hd.wait_for_user_selected_block([], ['Block 1', 'Block 2'],
@@ -2746,7 +2729,7 @@ if $PROGRAM_NAME == __FILE__
2746
2729
 
2747
2730
  def test_wait_for_user_selected_block_with_continue_state
2748
2731
  mock_block_state = Struct.new(:state, :block).new(
2749
- MenuState::CONTINUE, { dname: 'continue_block' }
2732
+ MenuState::CONTINUE, { oname: 'continue_block' }
2750
2733
  )
2751
2734
  @hd.stubs(:wait_for_user_selection).returns(mock_block_state)
2752
2735