markdown_exec 1.8 → 1.8.2

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.
@@ -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