markdown_exec 1.8.1 → 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,24 +302,10 @@ 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
386
308
  @run_state.started_at = Time.now.utc
387
- # rbp
388
309
 
389
310
  Open3.popen3(@delegate_object[:shell],
390
311
  '-c', command,
@@ -442,6 +363,23 @@ module MarkdownExec
442
363
  error_handler('command_or_user_selected_block')
443
364
  end
444
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
+
445
383
  def copy_to_clipboard(required_lines)
446
384
  text = required_lines.flatten.join($INPUT_RECORD_SEPARATOR)
447
385
  Clipboard.copy(text)
@@ -535,12 +473,17 @@ module MarkdownExec
535
473
  FileUtils.mkdir_p(File.dirname(file_path))
536
474
  end
537
475
 
538
- def write_file_content(file_path, content)
539
- File.write(file_path, content)
540
- 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]))
541
480
 
542
- def set_file_permissions(file_path, chmod_value)
543
- 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
+ )
544
487
  end
545
488
 
546
489
  # Creates a temporary file, writes the provided code blocks into it,
@@ -558,14 +501,6 @@ module MarkdownExec
558
501
  Dir::Tmpname.create(self.class.to_s) { |path| path }
559
502
  end
560
503
 
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
504
  # Updates the title of an FCB object from its body content if the title is nil or empty.
570
505
  def default_block_title_from_body(fcb)
571
506
  return unless fcb.title.nil? || fcb.title.empty?
@@ -595,16 +530,6 @@ module MarkdownExec
595
530
  error_handler('delete_required_temp_file')
596
531
  end
597
532
 
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
533
  # Determines the state of a selected block in the menu based on the selected option.
609
534
  # It categorizes the selected option into either EXIT, BACK, or CONTINUE state.
610
535
  #
@@ -636,12 +561,9 @@ module MarkdownExec
636
561
  :script_preview_frame_color)
637
562
  end
638
563
 
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
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?
645
567
  end
646
568
 
647
569
  def error_handler(name = '', opts = {})
@@ -666,29 +588,25 @@ module MarkdownExec
666
588
  post_execution_process
667
589
  end
668
590
 
669
- # private
670
-
671
- def set_script_block_name(selected)
672
- @run_state.script_block_name = selected[:oname]
673
- end
674
-
675
- def write_command_file_if_needed(lines)
676
- write_command_file(lines) if @delegate_object[:save_executed_script]
677
- end
678
-
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
687
-
688
- def post_execution_process
689
- initialize_and_save_execution_output
690
- output_execution_summary
691
- 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
692
610
  end
693
611
 
694
612
  # Retrieves a specific data symbol from the delegate object, converts it to a string,
@@ -705,6 +623,31 @@ module MarkdownExec
705
623
  string_send_color(data_string, color_sym)
706
624
  end
707
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
+
708
651
  # Formats a string based on a given context and applies color styling to it.
709
652
  # It retrieves format and color information from the delegate object and processes accordingly.
710
653
  #
@@ -745,133 +688,29 @@ module MarkdownExec
745
688
  @delegate_object[:block_name_match])
746
689
 
747
690
  fcb.stdin = extract_named_captures_from_option(titlexcall,
748
- @delegate_object[:block_stdin_scan])
749
- fcb.stdout = extract_named_captures_from_option(titlexcall,
750
- @delegate_object[:block_stdout_scan])
751
-
752
- shell_color_option = SHELL_COLOR_OPTIONS[fcb[:shell]]
753
- fcb.title = fcb.oname = bm && bm[1] ? bm[:title] : titlexcall
754
- fcb.dname = apply_shell_color_option(fcb.oname, shell_color_option)
755
-
756
- fcb
757
- end
758
-
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
- # Updates the delegate object's state based on the provided block state.
785
- # It sets the block name and determines if the user clicked the back link in the menu.
786
- #
787
- # @param block_state [Object] An object representing the state of a block in the menu.
788
- def handle_block_state(block_state)
789
- unless [MenuState::BACK,
790
- MenuState::CONTINUE].include?(block_state.state)
791
- return
792
- end
793
-
794
- @delegate_object[:block_name] = block_state.block[:oname]
795
- # rbp
796
- @menu_user_clicked_back_link = block_state.state == MenuState::BACK
797
- end
798
-
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
691
+ @delegate_object[:block_stdin_scan])
692
+ fcb.stdout = extract_named_captures_from_option(titlexcall,
693
+ @delegate_object[:block_stdout_scan])
862
694
 
863
- # 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)
864
698
 
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
699
+ fcb
869
700
  end
870
701
 
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)
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
875
714
  end
876
715
 
877
716
  def handle_stream(stream, file_type, swap: false)
@@ -896,10 +735,10 @@ module MarkdownExec
896
735
  end
897
736
  end
898
737
 
899
- def wait_for_stream_processing
900
- @process_mutex.synchronize do
901
- @process_cv.wait(@process_mutex)
902
- 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?
903
742
  end
904
743
 
905
744
  # Partitions the history state from the environment variable based on the document separator.
@@ -952,10 +791,6 @@ module MarkdownExec
952
791
  body.lines.map { |line| indent + line.chomp }.join("\n")
953
792
  end
954
793
 
955
- def initialize_fcb_names(fcb)
956
- fcb.oname = fcb.dname = fcb.title || ''
957
- end
958
-
959
794
  # Initializes variables for regex and other states
960
795
  def initial_state
961
796
  {
@@ -989,6 +824,10 @@ module MarkdownExec
989
824
  write_execution_output_to_file
990
825
  end
991
826
 
827
+ def initialize_fcb_names(fcb)
828
+ fcb.oname = fcb.dname = fcb.title || ''
829
+ end
830
+
992
831
  # Iterates through blocks in a file, applying the provided block to each line.
993
832
  # The iteration only occurs if the file exists.
994
833
  # @yield [Symbol] :filter Yields to obtain selected messages for processing.
@@ -1019,46 +858,11 @@ module MarkdownExec
1019
858
  block = block_find(all_blocks, :oname, block_name)
1020
859
  return unless block
1021
860
 
1022
- handle_opts_block(block, @delegate_object)
861
+ update_options_and_trigger_reuse(block, @delegate_object)
1023
862
  @most_recent_loaded_filename = @delegate_object[:filename]
1024
863
  true
1025
864
  end
1026
865
 
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
866
  def mdoc_and_blocks_from_nested_files
1063
867
  menu_blocks = blocks_from_nested_files
1064
868
  mdoc = MDoc.new(menu_blocks) do |nopts|
@@ -1074,9 +878,7 @@ module MarkdownExec
1074
878
 
1075
879
  # recreate menu with new options
1076
880
  #
1077
- if load_auto_blocks(all_blocks)
1078
- all_blocks, mdoc = mdoc_and_blocks_from_nested_files
1079
- end
881
+ all_blocks, mdoc = mdoc_and_blocks_from_nested_files if load_auto_blocks(all_blocks)
1080
882
 
1081
883
  menu_blocks = mdoc.fcbs_per_options(@delegate_object)
1082
884
  add_menu_chrome_blocks!(menu_blocks)
@@ -1108,14 +910,33 @@ module MarkdownExec
1108
910
  end
1109
911
  end
1110
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
+
1111
925
  def next_block_name_from_command_line_arguments
1112
926
  return MenuControl::Repeat unless @delegate_object[:input_cli_rest].present?
1113
927
 
1114
- # rbp
1115
928
  @delegate_object[:block_name] = @delegate_object[:input_cli_rest].pop
1116
929
  MenuControl::Fresh
1117
930
  end
1118
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
+
1119
940
  def output_execution_result
1120
941
  @fout.fout fetch_color(data_sym: :execution_report_preview_head,
1121
942
  color_sym: :execution_report_preview_frame_color)
@@ -1162,6 +983,27 @@ module MarkdownExec
1162
983
  ), level: level
1163
984
  end
1164
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
+
1165
1007
  # Prepare the blocks menu by adding labels and other necessary details.
1166
1008
  #
1167
1009
  # @param all_blocks [Array<Hash>] The list of blocks from the file.
@@ -1189,6 +1031,28 @@ module MarkdownExec
1189
1031
  end.compact
1190
1032
  end
1191
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
+
1192
1056
  ##
1193
1057
  # Presents a menu to the user for approving an action and performs additional tasks based on the selection.
1194
1058
  # The function provides options for approval, rejection, copying data to clipboard, or saving data to a file.
@@ -1247,13 +1111,27 @@ module MarkdownExec
1247
1111
  exit 1
1248
1112
  end
1249
1113
 
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
1114
  # public
1256
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
+
1257
1135
  # Reads required code blocks from a temporary file specified by an environment variable.
1258
1136
  # @return [Array<String>] Lines read from the temporary file, or an empty array if file is not found or path is empty.
1259
1137
  def read_required_blocks_from_temp_file
@@ -1269,6 +1147,29 @@ module MarkdownExec
1269
1147
  end
1270
1148
  end
1271
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
+
1272
1173
  # Evaluates the given string as Ruby code and rescues any StandardErrors.
1273
1174
  # If an error occurs, it calls the error_handler method with 'safeval'.
1274
1175
  # @param str [String] The string to be evaluated.
@@ -1285,17 +1186,22 @@ module MarkdownExec
1285
1186
  # error_handler('safeval')
1286
1187
  # end
1287
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
+
1288
1194
  # Select and execute a code block from a Markdown document.
1289
1195
  #
1290
1196
  # This method allows the user to interactively select a code block from a
1291
1197
  # Markdown document, obtain approval, and execute the chosen block of code.
1292
1198
  #
1293
1199
  # @return [Nil] Returns nil if no code block is selected or an error occurs.
1294
- def select_approve_and_execute_block(_execute: true)
1200
+ def select_execute_bash_and_special_blocks(_execute: true)
1295
1201
  @menu_base_options = @delegate_object
1296
1202
  repeat_menu = @menu_base_options[:block_name].present? ? MenuControl::Fresh : MenuControl::Repeat
1297
1203
  load_file_next_block = LoadFileNextBlock.new(LoadFile::Reuse)
1298
- default = nil
1204
+ menu_default_dname = nil
1299
1205
 
1300
1206
  @menu_state_filename = @menu_base_options[:filename]
1301
1207
  @menu_state_block_name = @menu_base_options[:block_name]
@@ -1304,7 +1210,6 @@ module MarkdownExec
1304
1210
  loop do
1305
1211
  @delegate_object = @menu_base_options.dup
1306
1212
  @menu_base_options[:filename] = @menu_state_filename
1307
- # rbp
1308
1213
  @menu_base_options[:block_name] = @menu_state_block_name
1309
1214
  @menu_state_filename = nil
1310
1215
  @menu_state_block_name = nil
@@ -1324,11 +1229,11 @@ module MarkdownExec
1324
1229
  )
1325
1230
  end
1326
1231
  block_state = command_or_user_selected_block(blocks_in_file,
1327
- menu_blocks, default)
1232
+ menu_blocks, menu_default_dname)
1328
1233
  return if block_state.state == MenuState::EXIT
1329
1234
 
1330
1235
  if block_state.block.nil?
1331
- 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]}",
1332
1237
  { abort: true })
1333
1238
  # error_handler("Block not found -- #{opts[:block_name]}", { abort: true })
1334
1239
  end
@@ -1337,11 +1242,13 @@ module MarkdownExec
1337
1242
  warn block_state.block.to_yaml.sub(/^(?:---\n)?/, "Block:\n")
1338
1243
  end
1339
1244
 
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
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
+
1345
1252
  @menu_base_options[:block_name] =
1346
1253
  @delegate_object[:block_name] = load_file_next_block.next_block
1347
1254
  @menu_base_options[:filename] = @delegate_object[:filename]
@@ -1369,7 +1276,7 @@ module MarkdownExec
1369
1276
  @menu_state_block_name = @menu_base_options[:block_name]
1370
1277
  end
1371
1278
  rescue StandardError
1372
- error_handler('select_approve_and_execute_block',
1279
+ error_handler('select_execute_bash_and_special_blocks',
1373
1280
  { abort: true })
1374
1281
  end
1375
1282
 
@@ -1400,6 +1307,38 @@ module MarkdownExec
1400
1307
  error_handler('select_option_with_metadata')
1401
1308
  end
1402
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
+
1403
1342
  # Initializes a new fenced code block (FCB) object based on the provided line and heading information.
1404
1343
  # @param line [String] The line initiating the fenced block.
1405
1344
  # @param headings [Array<String>] Current headings hierarchy.
@@ -1458,6 +1397,13 @@ module MarkdownExec
1458
1397
  symbols: { cross: ' ' }
1459
1398
  )
1460
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
1461
1407
 
1462
1408
  # Updates the hierarchy of document headings based on the given line.
1463
1409
  # Utilizes regular expressions to identify heading levels.
@@ -1555,6 +1501,25 @@ module MarkdownExec
1555
1501
  yield_to_block_if_applicable(fcb, selected_messages, &block)
1556
1502
  end
1557
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
+
1558
1523
  def wait_for_user_selected_block(all_blocks, menu_blocks, default)
1559
1524
  block_state = wait_for_user_selection(all_blocks, menu_blocks, default)
1560
1525
  handle_block_state(block_state)
@@ -1590,7 +1555,6 @@ module MarkdownExec
1590
1555
  def write_command_file(required_lines)
1591
1556
  return unless @delegate_object[:save_executed_script]
1592
1557
 
1593
- # rbp
1594
1558
  time_now = Time.now.utc
1595
1559
  @run_state.saved_script_filename =
1596
1560
  SavedAsset.script_name(blockname: @delegate_object[:block_name],
@@ -1622,6 +1586,10 @@ module MarkdownExec
1622
1586
  error_handler('write_command_file')
1623
1587
  end
1624
1588
 
1589
+ def write_command_file_if_needed(lines)
1590
+ write_command_file(lines) if @delegate_object[:save_executed_script]
1591
+ end
1592
+
1625
1593
  def write_execution_output_to_file
1626
1594
  FileUtils.mkdir_p File.dirname(@delegate_object[:logged_stdout_filespec])
1627
1595
 
@@ -1637,6 +1605,10 @@ module MarkdownExec
1637
1605
  )
1638
1606
  end
1639
1607
 
1608
+ def write_file_content(file_path, content)
1609
+ File.write(file_path, content)
1610
+ end
1611
+
1640
1612
  # Writes required code blocks to a temporary file and sets an environment variable with its path.
1641
1613
  #
1642
1614
  # @param mdoc [Object] The Markdown document object.
@@ -1658,6 +1630,10 @@ module MarkdownExec
1658
1630
  create_temp_file_with_code(code_blocks)
1659
1631
  end
1660
1632
 
1633
+ def write_to_file(path, content)
1634
+ File.write(path, content)
1635
+ end
1636
+
1661
1637
  # Yields a line as a new block if the selected message type includes :line.
1662
1638
  # @param [String] line The line to be processed.
1663
1639
  # @param [Array<Symbol>] selected_messages A list of message types to check.
@@ -1720,26 +1696,26 @@ if $PROGRAM_NAME == __FILE__
1720
1696
  end
1721
1697
 
1722
1698
  # Test case for empty body
1723
- def test_handle_link_block_with_empty_body
1699
+ def test_push_link_history_and_trigger_load_with_empty_body
1724
1700
  assert_equal LoadFileNextBlock.new(LoadFile::Reuse, ''),
1725
- @hd.handle_link_block([], nil, false)
1701
+ @hd.push_link_history_and_trigger_load([], nil, false)
1726
1702
  end
1727
1703
 
1728
1704
  # Test case for non-empty body without 'file' key
1729
- def test_handle_link_block_without_file_key
1705
+ def test_push_link_history_and_trigger_load_without_file_key
1730
1706
  body = ["vars:\n KEY: VALUE"]
1731
1707
  assert_equal LoadFileNextBlock.new(LoadFile::Reuse, ''),
1732
- @hd.handle_link_block(body, nil, false)
1708
+ @hd.push_link_history_and_trigger_load(body, nil, false)
1733
1709
  end
1734
1710
 
1735
1711
  # Test case for non-empty body with 'file' key
1736
- def test_handle_link_block_with_file_key
1712
+ def test_push_link_history_and_trigger_load_with_file_key
1737
1713
  body = ["file: sample_file\nblock: sample_block\nvars:\n KEY: VALUE"]
1738
1714
  expected_result = LoadFileNextBlock.new(LoadFile::Load,
1739
1715
  'sample_block')
1740
1716
  # mdoc = MDoc.new()
1741
1717
  assert_equal expected_result,
1742
- @hd.handle_link_block(body, nil, FCB.new)
1718
+ @hd.push_link_history_and_trigger_load(body, nil, FCB.new)
1743
1719
  end
1744
1720
 
1745
1721
  def test_history_env_state_exist_with_value
@@ -2261,11 +2237,11 @@ if $PROGRAM_NAME == __FILE__
2261
2237
  @hd.stubs(:history_state_pop)
2262
2238
  end
2263
2239
 
2264
- def test_handle_back_link
2240
+ def test_pop_link_history_and_trigger_load
2265
2241
  # Verifying that history_state_pop is called
2266
2242
  @hd.expects(:history_state_pop).once
2267
2243
 
2268
- result = @hd.handle_back_link
2244
+ result = @hd.pop_link_history_and_trigger_load
2269
2245
 
2270
2246
  # Asserting the result is an instance of LoadFileNextBlock
2271
2247
  assert_instance_of LoadFileNextBlock, result
@@ -2320,7 +2296,7 @@ if $PROGRAM_NAME == __FILE__
2320
2296
  @selected_item = mock('FCB')
2321
2297
  end
2322
2298
 
2323
- def test_handle_generic_block_without_user_approval
2299
+ def test_compile_execute_bash_and_special_blocks_and_trigger_reuse_without_user_approval
2324
2300
  # Mock the delegate object configuration
2325
2301
  @hd.instance_variable_set(:@delegate_object,
2326
2302
  { output_script: false,
@@ -2330,7 +2306,7 @@ if $PROGRAM_NAME == __FILE__
2330
2306
  # Expectations and assertions go here
2331
2307
  end
2332
2308
 
2333
- def test_handle_generic_block_with_user_approval
2309
+ def test_compile_execute_bash_and_special_blocks_and_trigger_reuse_with_user_approval
2334
2310
  # Mock the delegate object configuration
2335
2311
  @hd.instance_variable_set(:@delegate_object,
2336
2312
  { output_script: false,
@@ -2340,7 +2316,7 @@ if $PROGRAM_NAME == __FILE__
2340
2316
  # Expectations and assertions go here
2341
2317
  end
2342
2318
 
2343
- def test_handle_generic_block_with_output_script
2319
+ def test_compile_execute_bash_and_special_blocks_and_trigger_reuse_with_output_script
2344
2320
  # Mock the delegate object configuration
2345
2321
  @hd.instance_variable_set(:@delegate_object,
2346
2322
  { output_script: true,
@@ -2361,11 +2337,11 @@ if $PROGRAM_NAME == __FILE__
2361
2337
  @hd.stubs(:print)
2362
2338
  end
2363
2339
 
2364
- def test_handle_opts_block
2340
+ def test_update_options_and_trigger_reuse
2365
2341
  selected = { body: ['option1: value1'] }
2366
2342
  tgt2 = {}
2367
2343
 
2368
- result = @hd.handle_opts_block(selected, tgt2)
2344
+ result = @hd.update_options_and_trigger_reuse(selected, tgt2)
2369
2345
 
2370
2346
  assert_instance_of LoadFileNextBlock, result
2371
2347
  assert_equal 'value1',
@@ -2373,11 +2349,11 @@ if $PROGRAM_NAME == __FILE__
2373
2349
  assert_equal 'value1', tgt2[:option1]
2374
2350
  end
2375
2351
 
2376
- def test_handle_opts_block_without_format
2352
+ def test_update_options_and_trigger_reuse_without_format
2377
2353
  selected = { body: ['option2: value2'] }
2378
2354
  @hd.instance_variable_set(:@delegate_object, {})
2379
2355
 
2380
- result = @hd.handle_opts_block(selected)
2356
+ result = @hd.update_options_and_trigger_reuse(selected)
2381
2357
 
2382
2358
  assert_instance_of LoadFileNextBlock, result
2383
2359
  assert_equal 'value2',
@@ -2544,7 +2520,7 @@ if $PROGRAM_NAME == __FILE__
2544
2520
  def setup
2545
2521
  @hd = HashDelegator.new
2546
2522
  @hd.stubs(:block_find).returns({})
2547
- @hd.stubs(:handle_opts_block)
2523
+ @hd.stubs(:update_options_and_trigger_reuse)
2548
2524
  end
2549
2525
 
2550
2526
  def test_load_auto_blocks_with_new_filename