markdown_exec 2.1.0 → 2.3.0

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.
@@ -29,13 +29,18 @@ require_relative 'fcb'
29
29
  require_relative 'filter'
30
30
  require_relative 'fout'
31
31
  require_relative 'hash'
32
+ require_relative 'hierarchy_string'
32
33
  require_relative 'link_history'
33
34
  require_relative 'mdoc'
35
+ require_relative 'namer'
34
36
  require_relative 'regexp'
35
37
  require_relative 'resize_terminal'
36
38
  require_relative 'std_out_err_logger'
37
39
  require_relative 'streams_out'
38
40
  require_relative 'string_util'
41
+ require_relative 'text_analyzer'
42
+
43
+ $pd = false unless defined?($pd)
39
44
 
40
45
  class String
41
46
  # Checks if the string is not empty.
@@ -54,7 +59,8 @@ module HashDelegatorSelf
54
59
  # @param color_key [String, Symbol] The key representing the desired color method in the color_methods hash.
55
60
  # @param default_method [String] (optional) Default color method to use if color_key is not found in color_methods. Defaults to 'plain'.
56
61
  # @return [String] The colored string.
57
- def apply_color_from_hash(string, color_methods, color_key, default_method: 'plain')
62
+ def apply_color_from_hash(string, color_methods, color_key,
63
+ default_method: 'plain')
58
64
  color_method = color_methods.fetch(color_key, default_method).to_sym
59
65
  string.to_s.send(color_method)
60
66
  end
@@ -78,17 +84,17 @@ module HashDelegatorSelf
78
84
  # colored_string = apply_color_from_hash(string, color_transformations, :red)
79
85
  # puts colored_string # This will print the string in red
80
86
 
81
- # Searches for the first element in a collection where the specified key matches a given value.
87
+ # Searches for the first element in a collection where the specified message sent to an element matches a given value.
82
88
  # This method is particularly useful for finding a specific hash-like object within an enumerable collection.
83
89
  # If no match is found, it returns a specified default value.
84
90
  #
85
91
  # @param blocks [Enumerable] The collection of hash-like objects to search.
86
- # @param key [Object] The key to search for in each element of the collection.
87
- # @param value [Object] The value to match against each element's corresponding key value.
92
+ # @param msg [Symbol, String] The message to send to each element of the collection.
93
+ # @param value [Object] The value to match against the result of the message sent to each element.
88
94
  # @param default [Object, nil] The default value to return if no match is found (optional).
89
95
  # @return [Object, nil] The first matching element or the default value if no match is found.
90
- def block_find(blocks, key, value, default = nil)
91
- blocks.find { |item| item[key] == value } || default
96
+ def block_find(blocks, msg, value, default = nil)
97
+ blocks.find { |item| item.send(msg) == value } || default
92
98
  end
93
99
 
94
100
  def code_merge(*bodies)
@@ -132,8 +138,10 @@ module HashDelegatorSelf
132
138
  # delete the current line if it is empty and the previous is also empty
133
139
  def delete_consecutive_blank_lines!(blocks_menu)
134
140
  blocks_menu.process_and_conditionally_delete! do |prev_item, current_item, _next_item|
135
- prev_item&.fetch(:chrome, nil) && !prev_item&.fetch(:oname).present? &&
136
- current_item&.fetch(:chrome, nil) && !current_item&.fetch(:oname).present?
141
+ prev_item&.fetch(:chrome, nil) &&
142
+ !(prev_item && prev_item.oname.present?) &&
143
+ current_item&.fetch(:chrome, nil) &&
144
+ !(current_item && current_item.oname.present?)
137
145
  end
138
146
  end
139
147
 
@@ -188,13 +196,14 @@ module HashDelegatorSelf
188
196
  merged.empty? ? [] : merged
189
197
  end
190
198
 
191
- def next_link_state(block_name_from_cli:, was_using_cli:, block_state:, block_name: nil)
199
+ def next_link_state(block_name_from_cli:, was_using_cli:, block_state:,
200
+ block_name: nil)
192
201
  # Set block_name based on block_name_from_cli
193
202
  block_name = @cli_block_name if block_name_from_cli
194
203
 
195
204
  # Determine the state of breaker based on was_using_cli and the block type
196
- # true only when block_name is nil, block_name_from_cli is false, was_using_cli is true, and the block_state.block[:shell] equals BlockType::BASH. In all other scenarios, breaker is false.
197
- breaker = !block_name && !block_name_from_cli && was_using_cli && block_state.block.fetch(:shell, nil) == BlockType::BASH
205
+ # true only when block_name is nil, block_name_from_cli is false, was_using_cli is true, and the block_state.block.shell equals BlockType::BASH. In all other scenarios, breaker is false.
206
+ breaker = !block_name && !block_name_from_cli && was_using_cli && block_state.block.shell == BlockType::BASH
198
207
 
199
208
  # Reset block_name_from_cli if the conditions are not met
200
209
  block_name_from_cli ||= false
@@ -282,7 +291,8 @@ module HashDelegatorSelf
282
291
  # @param fcb [Object] The fcb object whose attributes are to be updated.
283
292
  # @param selected_messages [Array<Symbol>] A list of message types to determine if yielding is applicable.
284
293
  # @param block [Block] An optional block to yield to if conditions are met.
285
- def update_menu_attrib_yield_selected(fcb:, messages:, configuration: {}, &block)
294
+ def update_menu_attrib_yield_selected(fcb:, messages:, configuration: {},
295
+ &block)
286
296
  initialize_fcb_names(fcb)
287
297
  return unless fcb.body
288
298
 
@@ -429,7 +439,9 @@ class StringWrapper
429
439
  words.each.with_index do |word, index|
430
440
  trial_length = word.length
431
441
  trial_length += @first_indent.length if index.zero?
432
- trial_length += current_line.length + 1 + @rest_indent.length if index != 0
442
+ if index != 0
443
+ trial_length += current_line.length + 1 + @rest_indent.length
444
+ end
433
445
  if trial_length > max_line_length && (words.count != 0)
434
446
  lines << current_line
435
447
  current_line = word
@@ -472,6 +484,7 @@ module MarkdownExec
472
484
 
473
485
  extend HashDelegatorSelf
474
486
  include CompactionHelpers
487
+ include TextAnalyzer
475
488
 
476
489
  def initialize(delegate_object = {})
477
490
  @delegate_object = delegate_object
@@ -480,7 +493,8 @@ module MarkdownExec
480
493
  @most_recent_loaded_filename = nil
481
494
  @pass_args = []
482
495
  @run_state = OpenStruct.new(
483
- link_history: []
496
+ link_history: [],
497
+ source: OpenStruct.new
484
498
  )
485
499
  @link_history = LinkHistory.new
486
500
  @fout = FOut.new(@delegate_object) ### slice only relevant keys
@@ -506,13 +520,18 @@ module MarkdownExec
506
520
  def add_menu_chrome_blocks!(menu_blocks:, link_state:)
507
521
  return unless @delegate_object[:menu_link_format].present?
508
522
 
509
- add_inherited_lines(menu_blocks: menu_blocks, link_state: link_state) if @delegate_object[:menu_with_inherited_lines]
523
+ if @delegate_object[:menu_with_inherited_lines]
524
+ add_inherited_lines(menu_blocks: menu_blocks,
525
+ link_state: link_state)
526
+ end
510
527
 
511
528
  # back before exit
512
529
  add_back_option(menu_blocks: menu_blocks) if should_add_back_option?
513
530
 
514
531
  # exit after other options
515
- add_exit_option(menu_blocks: menu_blocks) if @delegate_object[:menu_with_exit]
532
+ if @delegate_object[:menu_with_exit]
533
+ add_exit_option(menu_blocks: menu_blocks)
534
+ end
516
535
 
517
536
  add_dividers(menu_blocks: menu_blocks)
518
537
  end
@@ -588,6 +607,20 @@ module MarkdownExec
588
607
  else
589
608
  menu_blocks.push(chrome_block)
590
609
  end
610
+
611
+ chrome_block
612
+ end
613
+
614
+ # Appends a formatted divider to the specified position in a menu block array.
615
+ # The method checks for the presence of formatting options before appending.
616
+ #
617
+ # @param menu_blocks [Array] The array of menu block elements.
618
+ # @param position [Symbol] The position to insert the divider (:initial or :final).
619
+ def append_divider(menu_blocks:, position:)
620
+ return unless divider_formatting_present?(position)
621
+
622
+ divider = create_divider(position)
623
+ position == :initial ? menu_blocks.unshift(divider) : menu_blocks.push(divider)
591
624
  end
592
625
 
593
626
  # Appends a formatted divider to the specified position in a menu block array.
@@ -596,10 +629,10 @@ module MarkdownExec
596
629
  # @param menu_blocks [Array] The array of menu block elements.
597
630
  # @param position [Symbol] The position to insert the divider (:initial or :final).
598
631
  def append_inherited_lines(menu_blocks:, link_state:, position: top)
599
- return unless link_state.inherited_lines.present?
632
+ return unless link_state.inherited_lines_present?
600
633
 
601
634
  insert_at_top = @delegate_object[:menu_inherited_lines_at_top]
602
- chrome_blocks = link_state.inherited_lines.map do |line|
635
+ chrome_blocks = link_state.inherited_lines_map do |line|
603
636
  formatted = format(@delegate_object[:menu_inherited_lines_format],
604
637
  { line: line })
605
638
  FCB.new(
@@ -623,18 +656,6 @@ module MarkdownExec
623
656
  HashDelegator.error_handler('append_inherited_lines')
624
657
  end
625
658
 
626
- # Appends a formatted divider to the specified position in a menu block array.
627
- # The method checks for the presence of formatting options before appending.
628
- #
629
- # @param menu_blocks [Array] The array of menu block elements.
630
- # @param position [Symbol] The position to insert the divider (:initial or :final).
631
- def append_divider(menu_blocks:, position:)
632
- return unless divider_formatting_present?(position)
633
-
634
- divider = create_divider(position)
635
- position == :initial ? menu_blocks.unshift(divider) : menu_blocks.push(divider)
636
- end
637
-
638
659
  # private
639
660
 
640
661
  # Applies shell color options to the given string if applicable.
@@ -650,6 +671,16 @@ module MarkdownExec
650
671
  end
651
672
  end
652
673
 
674
+ def apply_tree_decorations(text, color_method, decor_patterns)
675
+ tree = HierarchyString.new([{ text: text, color: color_method }])
676
+ decor_patterns.each do |pc|
677
+ analyzed_hierarchy = TextAnalyzer.analyze_hierarchy(tree.substrings, pc[:pattern],
678
+ color_method, pc[:color_method])
679
+ tree = HierarchyString.new(analyzed_hierarchy)
680
+ end
681
+ tree.decorate
682
+ end
683
+
653
684
  def assign_key_value_in_bash(key, value)
654
685
  if value =~ /["$\\`]/
655
686
  # requiring ShellWords to write into Bash scripts
@@ -667,12 +698,13 @@ module MarkdownExec
667
698
  # @return [Array<FCB>] An array of FCB objects representing the blocks.
668
699
  def blocks_from_nested_files
669
700
  register_console_attributes(@delegate_object)
701
+ @decor_patterns_from_delegate_object_for_block_create = collect_line_decor_patterns(@delegate_object)
670
702
 
671
703
  blocks = []
672
704
  iter_blocks_from_nested_files do |btype, fcb|
673
705
  process_block_based_on_type(blocks, btype, fcb)
674
706
  end
675
- # &bc 'blocks.count:', blocks.count
707
+ # &bt blocks.count
676
708
  blocks
677
709
  rescue StandardError
678
710
  HashDelegator.error_handler('blocks_from_nested_files')
@@ -682,9 +714,8 @@ module MarkdownExec
682
714
  # if matched, the block returned has properties that it is from cli and not ui
683
715
  def block_state_for_name_from_cli(block_name)
684
716
  SelectedBlockMenuState.new(
685
- @dml_blocks_in_file.find do |item|
686
- block_name == item.pub_name
687
- end&.merge(
717
+ blocks_find_by_block_name(@dml_blocks_in_file, block_name),
718
+ OpenStruct.new(
688
719
  block_name_from_cli: true,
689
720
  block_name_from_ui: false
690
721
  ),
@@ -692,18 +723,28 @@ module MarkdownExec
692
723
  )
693
724
  end
694
725
 
726
+ def blocks_find_by_block_name(blocks, block_name)
727
+ @dml_blocks_in_file.find do |item|
728
+ # 2024-08-04 match oname for long block names
729
+ # 2024-08-04 match nickname for long block names
730
+ block_name == item.pub_name || block_name == item.nickname || block_name == item.oname
731
+ end
732
+ end
733
+
695
734
  # private
696
735
 
697
736
  def calc_logged_stdout_filename(block_name:)
698
737
  return unless @delegate_object[:saved_stdout_folder]
699
738
 
700
739
  @delegate_object[:logged_stdout_filename] =
701
- SavedAsset.new(blockname: block_name,
702
- filename: @delegate_object[:filename],
703
- prefix: @delegate_object[:logged_stdout_filename_prefix],
704
- time: Time.now.utc,
705
- exts: '.out.txt',
706
- saved_asset_format: @delegate_object[:saved_asset_format]).generate_name
740
+ SavedAsset.new(
741
+ blockname: block_name,
742
+ filename: @delegate_object[:filename],
743
+ prefix: @delegate_object[:logged_stdout_filename_prefix],
744
+ time: Time.now.utc,
745
+ exts: '.out.txt',
746
+ saved_asset_format: shell_escape_asset_format(@dml_link_state)
747
+ ).generate_name
707
748
 
708
749
  @logged_stdout_filespec =
709
750
  @delegate_object[:logged_stdout_filespec] =
@@ -731,19 +772,37 @@ module MarkdownExec
731
772
  true
732
773
  end
733
774
 
775
+ def collect_line_decor_patterns(delegate_object)
776
+ extract_patterns = lambda do |key|
777
+ return [] unless delegate_object[key].present?
778
+
779
+ HashDelegator.safeval(delegate_object[key]).map do |pc|
780
+ {
781
+ color_method: pc[:color_method].to_sym,
782
+ pattern: Regexp.new(pc[:pattern])
783
+ }
784
+ end
785
+ end
786
+
787
+ %i[line_decor_pre line_decor_main line_decor_post].flat_map do |key|
788
+ extract_patterns.call(key)
789
+ end
790
+ end
791
+
734
792
  # Collects required code lines based on the selected block and the delegate object's configuration.
735
793
  # If the block type is VARS, it also sets environment variables based on the block's content.
736
794
  #
737
795
  # @param mdoc [YourMDocClass] An instance of the MDoc class.
738
796
  # @param selected [Hash] The selected block.
739
797
  # @return [Array<String>] Required code blocks as an array of lines.
740
- def collect_required_code_lines(mdoc:, selected:, block_source:, link_state: LinkState.new)
798
+ def collect_required_code_lines(mdoc:, selected:, block_source:,
799
+ link_state: LinkState.new)
741
800
  required = mdoc.collect_recursively_required_code(
742
801
  anyname: selected.pub_name,
743
802
  label_format_above: @delegate_object[:shell_code_label_format_above],
744
803
  label_format_below: @delegate_object[:shell_code_label_format_below],
745
804
  block_source: block_source
746
- )
805
+ ) # &bt 'required'
747
806
  dependencies = (link_state&.inherited_dependencies || {}).merge(required[:dependencies] || {})
748
807
  required[:unmet_dependencies] =
749
808
  (required[:unmet_dependencies] || []) - (link_state&.inherited_block_names || [])
@@ -755,14 +814,19 @@ module MarkdownExec
755
814
  runtime_exception(:runtime_exception_error_level,
756
815
  'unmet_dependencies, flag: runtime_exception_error_level',
757
816
  required[:unmet_dependencies])
758
- else
817
+ elsif false ### use option 2024-08-02
759
818
  warn format_and_highlight_dependencies(dependencies,
760
819
  highlight: [@delegate_object[:block_name]])
761
820
  end
762
821
 
763
- code_lines = selected[:shell] == BlockType::VARS ? set_environment_variables_for_block(selected) : []
764
-
765
- HashDelegator.code_merge(link_state&.inherited_lines, required[:code] + code_lines)
822
+ if selected[:shell] == BlockType::OPTS
823
+ # body of blocks is returned as a list of lines to be read an YAML
824
+ HashDelegator.code_merge(required[:blocks].map(&:body).flatten(1))
825
+ else
826
+ code_lines = selected.shell == BlockType::VARS ? set_environment_variables_for_block(selected) : []
827
+ HashDelegator.code_merge(link_state&.inherited_lines,
828
+ required[:code] + code_lines)
829
+ end
766
830
  end
767
831
 
768
832
  def command_execute(command, args: [])
@@ -783,7 +847,8 @@ module MarkdownExec
783
847
  else
784
848
  @run_state.in_own_window = false
785
849
  execute_command_with_streams(
786
- [@delegate_object[:shell], '-c', command, @delegate_object[:filename], *args]
850
+ [@delegate_object[:shell], '-c', command,
851
+ @delegate_object[:filename], *args]
787
852
  )
788
853
  end
789
854
 
@@ -793,14 +858,16 @@ module MarkdownExec
793
858
  @run_state.aborted_at = Time.now.utc
794
859
  @run_state.error_message = err.message
795
860
  @run_state.error = err
796
- @run_state.files.append_stream_line(ExecutionStreams::STD_ERR, @run_state.error_message)
861
+ @run_state.files.append_stream_line(ExecutionStreams::STD_ERR,
862
+ @run_state.error_message)
797
863
  @fout.fout "Error ENOENT: #{err.inspect}"
798
864
  rescue SignalException => err
799
865
  # Handle SignalException
800
866
  @run_state.aborted_at = Time.now.utc
801
867
  @run_state.error_message = 'SIGTERM'
802
868
  @run_state.error = err
803
- @run_state.files.append_stream_line(ExecutionStreams::STD_ERR, @run_state.error_message)
869
+ @run_state.files.append_stream_line(ExecutionStreams::STD_ERR,
870
+ @run_state.error_message)
804
871
  @fout.fout "Error ENOENT: #{err.inspect}"
805
872
  end
806
873
 
@@ -830,18 +897,25 @@ module MarkdownExec
830
897
  # @param mdoc [Object] The markdown document object containing code blocks.
831
898
  # @param selected [Hash] The selected item from the menu to be executed.
832
899
  # @return [LoadFileLinkState] An object indicating whether to load the next block or reuse the current one.
833
- def compile_execute_and_trigger_reuse(mdoc:, selected:, block_source:, link_state: nil)
900
+ def compile_execute_and_trigger_reuse(mdoc:, selected:, block_source:,
901
+ link_state: nil)
834
902
  required_lines = collect_required_code_lines(mdoc: mdoc, selected: selected, link_state: link_state,
835
903
  block_source: block_source)
836
904
  output_or_approval = @delegate_object[:output_script] || @delegate_object[:user_must_approve]
837
- display_required_code(required_lines: required_lines) if output_or_approval
905
+ if output_or_approval
906
+ display_required_code(required_lines: required_lines)
907
+ end
838
908
  allow_execution = if @delegate_object[:user_must_approve]
839
- prompt_for_user_approval(required_lines: required_lines, selected: selected)
909
+ prompt_for_user_approval(required_lines: required_lines,
910
+ selected: selected)
840
911
  else
841
912
  true
842
913
  end
843
914
 
844
- execute_required_lines(required_lines: required_lines, selected: selected) if allow_execution
915
+ if allow_execution
916
+ execute_required_lines(required_lines: required_lines,
917
+ selected: selected)
918
+ end
845
919
 
846
920
  link_state.block_name = nil
847
921
  LoadFileLinkState.new(LoadFile::REUSE, link_state)
@@ -883,6 +957,7 @@ module MarkdownExec
883
957
  format_option:, color_method:,
884
958
  case_conversion: nil,
885
959
  center: nil,
960
+ decor_patterns: [],
886
961
  wrap: nil)
887
962
  line_cap = match_data.named_captures.transform_keys(&:to_sym)
888
963
 
@@ -933,11 +1008,14 @@ module MarkdownExec
933
1008
  # format expects :line to be text only
934
1009
  line_obj[:line] = line_obj[:text]
935
1010
  oname = format(format_option, line_obj)
1011
+
1012
+ decorated = apply_tree_decorations(oname, color_method, decor_patterns)
1013
+
936
1014
  line_obj[:line] = line_obj[:indent] + line_obj[:text]
937
1015
  blocks.push FCB.new(
938
1016
  chrome: true,
939
1017
  disabled: '',
940
- dname: line_obj[:indent] + oname.send(color_method),
1018
+ dname: line_obj[:indent] + decorated,
941
1019
  oname: line_obj[:text]
942
1020
  )
943
1021
  end
@@ -951,6 +1029,7 @@ module MarkdownExec
951
1029
  # @param opts [Hash] Options containing configuration for line processing.
952
1030
  # @param use_chrome [Boolean] Indicates if the chrome styling should be applied.
953
1031
  def create_and_add_chrome_blocks(blocks, fcb)
1032
+ # rubocop:disable Layout/LineLength
954
1033
  match_criteria = [
955
1034
  { color: :menu_heading1_color, format: :menu_heading1_format, match: :heading1_match, center: true, case_conversion: :upcase, wrap: true },
956
1035
  { color: :menu_heading2_color, format: :menu_heading2_format, match: :heading2_match, center: true, wrap: true },
@@ -959,6 +1038,7 @@ module MarkdownExec
959
1038
  { color: :menu_note_color, format: :menu_note_format, match: :menu_note_match, wrap: true },
960
1039
  { color: :menu_task_color, format: :menu_task_format, match: :menu_task_match, wrap: true }
961
1040
  ]
1041
+ # rubocop:enable Layout/LineLength
962
1042
  # rubocop:enable Style/UnlessElse
963
1043
  match_criteria.each do |criteria|
964
1044
  unless @delegate_object[criteria[:match]].present? &&
@@ -971,6 +1051,7 @@ module MarkdownExec
971
1051
  case_conversion: criteria[:case_conversion],
972
1052
  center: criteria[:center],
973
1053
  color_method: @delegate_object[criteria[:color]].to_sym,
1054
+ decor_patterns: @decor_patterns_from_delegate_object_for_block_create,
974
1055
  format_option: @delegate_object[criteria[:format]],
975
1056
  match_data: mbody,
976
1057
  wrap: criteria[:wrap]
@@ -999,10 +1080,12 @@ module MarkdownExec
999
1080
  return true unless @delegate_object[:debounce_execution]
1000
1081
 
1001
1082
  # filter block if selected in menu
1002
- return true if @run_state.block_name_from_cli
1083
+ return true if @run_state.source.block_name_from_cli
1003
1084
 
1004
1085
  # return false if @prior_execution_block == @delegate_object[:block_name]
1005
- return @allowed_execution_block == @prior_execution_block || prompt_approve_repeat if @prior_execution_block == @delegate_object[:block_name]
1086
+ if @prior_execution_block == @delegate_object[:block_name]
1087
+ return @allowed_execution_block == @prior_execution_block || prompt_approve_repeat
1088
+ end
1006
1089
 
1007
1090
  @prior_execution_block = @delegate_object[:block_name]
1008
1091
  @allowed_execution_block = nil
@@ -1019,17 +1102,21 @@ module MarkdownExec
1019
1102
  # @param selected_option [Hash] The selected menu option.
1020
1103
  # @return [SelectedBlockMenuState] An object representing the state of the selected block.
1021
1104
  def determine_block_state(selected_option)
1022
- option_name = selected_option.fetch(:oname, nil)
1105
+ option_name = selected_option[:oname]
1023
1106
  if option_name == menu_chrome_formatted_option(:menu_option_exit_name)
1024
1107
  return SelectedBlockMenuState.new(nil,
1108
+ OpenStruct.new,
1025
1109
  MenuState::EXIT)
1026
1110
  end
1027
1111
  if option_name == menu_chrome_formatted_option(:menu_option_back_name)
1028
1112
  return SelectedBlockMenuState.new(selected_option,
1113
+ OpenStruct.new,
1029
1114
  MenuState::BACK)
1030
1115
  end
1031
1116
 
1032
- SelectedBlockMenuState.new(selected_option, MenuState::CONTINUE)
1117
+ SelectedBlockMenuState.new(selected_option,
1118
+ OpenStruct.new,
1119
+ MenuState::CONTINUE)
1033
1120
  end
1034
1121
 
1035
1122
  # Displays the required lines of code with color formatting for the preview section.
@@ -1068,9 +1155,9 @@ module MarkdownExec
1068
1155
  block_name: @delegate_object[:block_name],
1069
1156
  document_filename: @delegate_object[:filename]
1070
1157
  )
1071
- @run_state.block_name_from_cli = @dml_link_state.block_name.present?
1158
+ @run_state.source.block_name_from_cli = @dml_link_state.block_name.present?
1072
1159
  @cli_block_name = @dml_link_state.block_name
1073
- @dml_now_using_cli = @run_state.block_name_from_cli
1160
+ @dml_now_using_cli = @run_state.source.block_name_from_cli
1074
1161
  @dml_menu_default_dname = nil
1075
1162
  @dml_block_state = SelectedBlockMenuState.new
1076
1163
  @doc_saved_lines_files = []
@@ -1078,18 +1165,28 @@ module MarkdownExec
1078
1165
  ## load file with code lines per options
1079
1166
  #
1080
1167
  if @menu_base_options[:load_code].present?
1081
- @dml_link_state.inherited_lines = []
1082
- @menu_base_options[:load_code].split(':').map do |path|
1083
- @dml_link_state.inherited_lines += File.readlines(path, chomp: true)
1084
- end
1168
+ @dml_link_state.inherited_lines =
1169
+ @menu_base_options[:load_code].split(':').map do |path|
1170
+ File.readlines(path, chomp: true)
1171
+ end.flatten(1)
1085
1172
 
1086
1173
  inherited_block_names = []
1087
1174
  inherited_dependencies = {}
1088
- selected = { oname: 'load_code' }
1089
- pop_add_current_code_to_head_and_trigger_load(@dml_link_state, inherited_block_names, code_lines, inherited_dependencies, selected)
1090
- end
1091
-
1092
- fdo = ->(mo) { format(@delegate_object[:menu_link_format], HashDelegator.safeval(@delegate_object[mo])) }
1175
+ selected = FCB.new(oname: 'load_code')
1176
+ pop_add_current_code_to_head_and_trigger_load(@dml_link_state, inherited_block_names,
1177
+ code_lines, inherited_dependencies, selected)
1178
+ end
1179
+
1180
+ fdo = ->(option) {
1181
+ name = format(@delegate_object[:menu_link_format],
1182
+ HashDelegator.safeval(@delegate_object[option]))
1183
+ OpenStruct.new(
1184
+ dname: name,
1185
+ oname: name,
1186
+ name: name,
1187
+ pub_name: name.pub_name
1188
+ )
1189
+ }
1093
1190
  item_back = fdo.call(:menu_option_back_name)
1094
1191
  item_edit = fdo.call(:menu_option_edit_name)
1095
1192
  item_history = fdo.call(:menu_option_history_name)
@@ -1101,36 +1198,65 @@ module MarkdownExec
1101
1198
  @run_state.batch_random = Random.new.rand
1102
1199
  @run_state.batch_index = 0
1103
1200
 
1201
+ @run_state.files = StreamsOut.new
1202
+
1104
1203
  InputSequencer.new(
1105
1204
  @delegate_object[:filename],
1106
1205
  @delegate_object[:input_cli_rest]
1107
1206
  ).run do |msg, data|
1207
+ # &bt msg
1108
1208
  case msg
1109
1209
  when :parse_document # once for each menu
1110
1210
  # puts "@ - parse document #{data}"
1111
1211
  inpseq_parse_document(data)
1112
1212
 
1113
1213
  if @delegate_object[:menu_for_history]
1114
- history_files.tap do |files|
1115
- menu_enable_option(item_history, files.count, 'files', menu_state: MenuState::HISTORY) if files.count.positive?
1214
+ history_files(@dml_link_state).tap do |files|
1215
+ if files.count.positive?
1216
+ menu_enable_option(item_history.oname, files.count, 'files',
1217
+ menu_state: MenuState::HISTORY)
1218
+ end
1116
1219
  end
1117
1220
  end
1118
1221
 
1119
1222
  if @delegate_object[:menu_for_saved_lines] && @delegate_object[:document_saved_lines_glob].present?
1120
1223
 
1121
- sf = document_name_in_glob_as_file_name(@dml_link_state.document_filename, @delegate_object[:document_saved_lines_glob])
1224
+ sf = document_name_in_glob_as_file_name(
1225
+ @dml_link_state.document_filename,
1226
+ @delegate_object[:document_saved_lines_glob]
1227
+ )
1122
1228
  files = sf ? Dir.glob(sf) : []
1123
1229
  @doc_saved_lines_files = files.count.positive? ? files : []
1124
1230
 
1125
- lines_count = @dml_link_state.inherited_lines&.count || 0
1231
+ lines_count = @dml_link_state.inherited_lines_count
1126
1232
 
1127
1233
  # add menu items (glob, load, save) and enable selectively
1128
- menu_add_disabled_option(sf) if files.count.positive? || lines_count.positive?
1129
- menu_enable_option(item_load, files.count, 'files', menu_state: MenuState::LOAD) if files.count.positive?
1130
- menu_enable_option(item_edit, lines_count, 'lines', menu_state: MenuState::EDIT) if lines_count.positive?
1131
- menu_enable_option(item_save, 1, '', menu_state: MenuState::SAVE) if lines_count.positive?
1132
- menu_enable_option(item_view, 1, '', menu_state: MenuState::VIEW) if lines_count.positive?
1133
- menu_enable_option(item_shell, 1, '', menu_state: MenuState::SHELL) if @delegate_object[:menu_with_shell]
1234
+ if files.count.positive? || lines_count.positive?
1235
+ menu_add_disabled_option(sf)
1236
+ end
1237
+ if files.count.positive?
1238
+ menu_enable_option(item_load.dname, files.count, 'files',
1239
+ menu_state: MenuState::LOAD)
1240
+ end
1241
+ if lines_count.positive?
1242
+ menu_enable_option(item_edit.dname, lines_count, 'lines',
1243
+ menu_state: MenuState::EDIT)
1244
+ end
1245
+ if lines_count.positive?
1246
+ menu_enable_option(item_save.dname, 1, '',
1247
+ menu_state: MenuState::SAVE)
1248
+ end
1249
+ if lines_count.positive?
1250
+ menu_enable_option(item_view.dname, 1, '',
1251
+ menu_state: MenuState::VIEW)
1252
+ end
1253
+ if @delegate_object[:menu_with_shell]
1254
+ menu_enable_option(item_shell.dname, 1, '',
1255
+ menu_state: MenuState::SHELL)
1256
+ end
1257
+
1258
+ # # reflect new menu items
1259
+ # @dml_mdoc = MDoc.new(@dml_menu_blocks)
1134
1260
  end
1135
1261
 
1136
1262
  when :display_menu
@@ -1142,9 +1268,8 @@ module MarkdownExec
1142
1268
  when :user_choice
1143
1269
  if @dml_link_state.block_name.present?
1144
1270
  # @prior_block_was_link = true
1145
- @dml_block_state.block = @dml_blocks_in_file.find do |item|
1146
- item.pub_name == @dml_link_state.block_name
1147
- end
1271
+ @dml_block_state.block = blocks_find_by_block_name(@dml_blocks_in_file,
1272
+ @dml_link_state.block_name)
1148
1273
  @dml_link_state.block_name = nil
1149
1274
  else
1150
1275
  # puts "? - Select a block to execute (or type #{$texit} to exit):"
@@ -1156,7 +1281,7 @@ module MarkdownExec
1156
1281
 
1157
1282
  when :execute_block
1158
1283
  case (block_name = data)
1159
- when item_back
1284
+ when item_back.pub_name
1160
1285
  debounce_reset
1161
1286
  @menu_user_clicked_back_link = true
1162
1287
  load_file_link_state = pop_link_history_and_trigger_load
@@ -1171,64 +1296,94 @@ module MarkdownExec
1171
1296
  )
1172
1297
  )
1173
1298
 
1174
- when item_edit
1299
+ when item_edit.pub_name
1175
1300
  debounce_reset
1176
- edited = edit_text(@dml_link_state.inherited_lines.join("\n"))
1301
+ edited = edit_text(@dml_link_state.inherited_lines_block)
1177
1302
  @dml_link_state.inherited_lines = edited.split("\n") if edited
1178
1303
 
1179
1304
  return :break if pause_user_exit
1180
1305
 
1181
1306
  InputSequencer.next_link_state(prior_block_was_link: true)
1182
1307
 
1183
- when item_history
1308
+ when item_history.pub_name
1184
1309
  debounce_reset
1185
- files = history_files
1310
+ files = history_files(@dml_link_state)
1186
1311
  files_table_rows = files.map do |file|
1187
1312
  if Regexp.new(@delegate_object[:saved_asset_match]) =~ file
1188
- OpenStruct.new(file: file, row: [$~[:time], $~[:blockname], $~[:exts]].join(' '))
1313
+ begin
1314
+ OpenStruct.new(
1315
+ file: file,
1316
+ row: format(
1317
+ @delegate_object[:saved_history_format],
1318
+ # create with default '*' so unknown parameters are given a wildcard
1319
+ $~.names.each_with_object(Hash.new('*')) do |name, hash|
1320
+ hash[name.to_sym] = $~[name]
1321
+ end
1322
+ )
1323
+ )
1324
+ rescue KeyError
1325
+ # pp $!, $@
1326
+ warn "Cannot format with: #{@delegate_object[:saved_history_format]}"
1327
+ error_handler('saved_history_format')
1328
+ break
1329
+ end
1189
1330
  else
1190
1331
  warn "Cannot parse name: #{file}"
1191
1332
  next
1192
1333
  end
1193
- end.compact
1194
-
1195
- case (name = prompt_select_code_filename(
1196
- [@delegate_object[:prompt_filespec_back]] +
1197
- files_table_rows.map(&:row),
1198
- string: @delegate_object[:prompt_select_history_file],
1199
- color_sym: :prompt_color_after_script_execution
1200
- ))
1201
- when @delegate_object[:prompt_filespec_back]
1202
- # do nothing
1203
- else
1204
- file = files_table_rows.select { |ftr| ftr.row == name }&.first
1205
- info = file_info(file.file)
1206
- warn "#{file.file} - #{info[:lines]} lines / #{info[:size]} bytes"
1207
- warn(File.readlines(file.file, chomp: false).map.with_index do |line, ind|
1208
- format(' %s. %s', format('% 4d', ind).violet, line)
1209
- end)
1334
+ end&.compact
1335
+
1336
+ return :break unless files_table_rows
1337
+
1338
+ # repeat select+display until user exits
1339
+ row_attrib = :row
1340
+ loop do
1341
+ # menu with Back and Facet options at top
1342
+ case (name = prompt_select_code_filename(
1343
+ [@delegate_object[:prompt_filespec_back],
1344
+ @delegate_object[:prompt_filespec_facet]] +
1345
+ files_table_rows.map(&row_attrib),
1346
+ string: @delegate_object[:prompt_select_history_file],
1347
+ color_sym: :prompt_color_after_script_execution
1348
+ ))
1349
+ when @delegate_object[:prompt_filespec_back]
1350
+ break
1351
+ when @delegate_object[:prompt_filespec_facet]
1352
+ row_attrib = row_attrib == :row ? :file : :row
1353
+ else
1354
+ file = files_table_rows.select { |ftr| ftr.row == name }&.first
1355
+ info = file_info(file.file)
1356
+ warn "#{file.file} - #{info[:lines]} lines / #{info[:size]} bytes"
1357
+ warn(File.readlines(file.file,
1358
+ chomp: false).map.with_index do |line, ind|
1359
+ format(' %s. %s', format('% 4d', ind + 1).violet, line)
1360
+ end)
1361
+ end
1210
1362
  end
1211
1363
 
1212
1364
  return :break if pause_user_exit
1213
1365
 
1214
1366
  InputSequencer.next_link_state(prior_block_was_link: true)
1215
1367
 
1216
- when item_load
1368
+ when item_load.pub_name
1217
1369
  debounce_reset
1218
- sf = document_name_in_glob_as_file_name(@dml_link_state.document_filename, @delegate_object[:document_saved_lines_glob])
1370
+ sf = document_name_in_glob_as_file_name(@dml_link_state.document_filename,
1371
+ @delegate_object[:document_saved_lines_glob])
1219
1372
  load_filespec = load_filespec_from_expression(sf)
1220
1373
  if load_filespec
1221
- @dml_link_state.inherited_lines ||= []
1222
- @dml_link_state.inherited_lines += File.readlines(load_filespec, chomp: true)
1374
+ @dml_link_state.inherited_lines_append(
1375
+ File.readlines(load_filespec, chomp: true)
1376
+ )
1223
1377
  end
1224
1378
 
1225
1379
  return :break if pause_user_exit
1226
1380
 
1227
1381
  InputSequencer.next_link_state(prior_block_was_link: true)
1228
1382
 
1229
- when item_save
1383
+ when item_save.pub_name
1230
1384
  debounce_reset
1231
- sf = document_name_in_glob_as_file_name(@dml_link_state.document_filename, @delegate_object[:document_saved_lines_glob])
1385
+ sf = document_name_in_glob_as_file_name(@dml_link_state.document_filename,
1386
+ @delegate_object[:document_saved_lines_glob])
1232
1387
  save_filespec = save_filespec_from_expression(sf)
1233
1388
  if save_filespec && !write_file_with_directory_creation(
1234
1389
  save_filespec,
@@ -1240,7 +1395,7 @@ module MarkdownExec
1240
1395
 
1241
1396
  InputSequencer.next_link_state(prior_block_was_link: true)
1242
1397
 
1243
- when item_shell
1398
+ when item_shell.pub_name
1244
1399
  debounce_reset
1245
1400
  loop do
1246
1401
  command = prompt_for_command(":MDE #{Time.now.strftime('%FT%TZ')}> ".bgreen)
@@ -1261,9 +1416,9 @@ module MarkdownExec
1261
1416
 
1262
1417
  InputSequencer.next_link_state(prior_block_was_link: true)
1263
1418
 
1264
- when item_view
1419
+ when item_view.pub_name
1265
1420
  debounce_reset
1266
- warn @dml_link_state.inherited_lines.join("\n")
1421
+ warn @dml_link_state.inherited_lines_block
1267
1422
 
1268
1423
  return :break if pause_user_exit
1269
1424
 
@@ -1271,28 +1426,28 @@ module MarkdownExec
1271
1426
 
1272
1427
  else
1273
1428
  @dml_block_state = block_state_for_name_from_cli(block_name)
1274
- if @dml_block_state.block && @dml_block_state.block.fetch(:shell, nil) == BlockType::OPTS
1429
+ if @dml_block_state.block && @dml_block_state.block.shell == BlockType::OPTS
1275
1430
  debounce_reset
1276
1431
  link_state = LinkState.new
1277
1432
  options_state = read_show_options_and_trigger_reuse(
1278
- selected: @dml_block_state.block,
1279
- link_state: link_state
1433
+ link_state: link_state,
1434
+ mdoc: @dml_mdoc,
1435
+ selected: @dml_block_state.block
1280
1436
  )
1281
1437
 
1282
- @menu_base_options.merge!(options_state.options)
1283
- @delegate_object.merge!(options_state.options)
1438
+ update_menu_base(options_state.options)
1284
1439
  options_state.load_file_link_state.link_state
1285
1440
  else
1286
1441
  inpseq_execute_block(block_name)
1287
1442
 
1288
- if prompt_user_exit(block_name_from_cli: @run_state.block_name_from_cli,
1443
+ if prompt_user_exit(block_name_from_cli: @run_state.source.block_name_from_cli,
1289
1444
  selected: @dml_block_state.block)
1290
1445
  return :break
1291
1446
  end
1292
1447
 
1293
1448
  ## order of block name processing: link block, cli, from user
1294
1449
  #
1295
- @dml_link_state.block_name, @run_state.block_name_from_cli, cli_break = \
1450
+ @dml_link_state.block_name, @run_state.source.block_name_from_cli, cli_break =
1296
1451
  HashDelegator.next_link_state(
1297
1452
  block_name: @dml_link_state.block_name,
1298
1453
  block_name_from_cli: @dml_now_using_cli,
@@ -1300,14 +1455,14 @@ module MarkdownExec
1300
1455
  was_using_cli: @dml_now_using_cli
1301
1456
  )
1302
1457
 
1303
- if !@dml_block_state.block[:block_name_from_ui] && cli_break
1458
+ if !@dml_block_state.source.block_name_from_ui && cli_break
1304
1459
  # &bsp '!block_name_from_ui + cli_break -> break'
1305
1460
  return :break
1306
1461
  end
1307
1462
 
1308
1463
  InputSequencer.next_link_state(
1309
1464
  block_name: @dml_link_state.block_name,
1310
- prior_block_was_link: @dml_block_state.block.fetch(:shell, nil) != BlockType::BASH
1465
+ prior_block_was_link: @dml_block_state.block.shell != BlockType::BASH
1311
1466
  )
1312
1467
  end
1313
1468
  end
@@ -1328,9 +1483,13 @@ module MarkdownExec
1328
1483
  # remove leading "./"
1329
1484
  # replace characters: / : . * (space) with: (underscore)
1330
1485
  def document_name_in_glob_as_file_name(document_filename, glob)
1331
- return document_filename if document_filename.nil? || document_filename.empty?
1486
+ if document_filename.nil? || document_filename.empty?
1487
+ return document_filename
1488
+ end
1332
1489
 
1333
- format(glob, { document_filename: document_filename.gsub(%r{^\./}, '').gsub(/[\/:\.\* ]/, '_') })
1490
+ format(glob,
1491
+ { document_filename: document_filename.gsub(%r{^\./}, '').gsub(/[\/:\.\* ]/,
1492
+ '_') })
1334
1493
  end
1335
1494
 
1336
1495
  def dump_and_warn_block_state(selected:)
@@ -1351,7 +1510,10 @@ module MarkdownExec
1351
1510
  # @param menu_blocks [Hash] Hash of menu blocks.
1352
1511
  # @param link_state [LinkState] Current state of the link.
1353
1512
  def dump_delobj(blocks_in_file, menu_blocks, link_state)
1354
- warn format_and_highlight_hash(@delegate_object, label: '@delegate_object') if @delegate_object[:dump_delegate_object]
1513
+ if @delegate_object[:dump_delegate_object]
1514
+ warn format_and_highlight_hash(@delegate_object,
1515
+ label: '@delegate_object')
1516
+ end
1355
1517
 
1356
1518
  if @delegate_object[:dump_blocks_in_file]
1357
1519
  warn format_and_highlight_dependencies(compact_and_index_hash(blocks_in_file),
@@ -1363,11 +1525,18 @@ module MarkdownExec
1363
1525
  label: 'menu_blocks')
1364
1526
  end
1365
1527
 
1366
- warn format_and_highlight_lines(link_state.inherited_block_names, label: 'inherited_block_names') if @delegate_object[:dump_inherited_block_names]
1367
- warn format_and_highlight_lines(link_state.inherited_dependencies, label: 'inherited_dependencies') if @delegate_object[:dump_inherited_dependencies]
1528
+ if @delegate_object[:dump_inherited_block_names]
1529
+ warn format_and_highlight_lines(link_state.inherited_block_names,
1530
+ label: 'inherited_block_names')
1531
+ end
1532
+ if @delegate_object[:dump_inherited_dependencies]
1533
+ warn format_and_highlight_lines(link_state.inherited_dependencies,
1534
+ label: 'inherited_dependencies')
1535
+ end
1368
1536
  return unless @delegate_object[:dump_inherited_lines]
1369
1537
 
1370
- warn format_and_highlight_lines(link_state.inherited_lines, label: 'inherited_lines')
1538
+ warn format_and_highlight_lines(link_state.inherited_lines,
1539
+ label: 'inherited_lines')
1371
1540
  end
1372
1541
 
1373
1542
  # Opens text in an editor for user modification and returns the modified text.
@@ -1432,7 +1601,7 @@ module MarkdownExec
1432
1601
 
1433
1602
  # if the same menu is being displayed, collect the display name of the selected menu item for use as the default item
1434
1603
  [lfls.link_state,
1435
- lfls.load_file == LoadFile::LOAD ? nil : selected[:dname]]
1604
+ lfls.load_file == LoadFile::LOAD ? nil : selected.dname]
1436
1605
  #.tap { |ret| pp [__FILE__,__LINE__,'exec_bash_next_state()',ret] }
1437
1606
  end
1438
1607
 
@@ -1453,17 +1622,20 @@ module MarkdownExec
1453
1622
 
1454
1623
  Open3.popen3(*command) do |stdin, stdout, stderr, exec_thread|
1455
1624
  # Handle stdout stream
1456
- handle_stream(stream: stdout, file_type: ExecutionStreams::STD_OUT) do |line|
1625
+ handle_stream(stream: stdout,
1626
+ file_type: ExecutionStreams::STD_OUT) do |line|
1457
1627
  yield nil, line, nil, exec_thread if block_given?
1458
1628
  end
1459
1629
 
1460
1630
  # Handle stderr stream
1461
- handle_stream(stream: stderr, file_type: ExecutionStreams::STD_ERR) do |line|
1631
+ handle_stream(stream: stderr,
1632
+ file_type: ExecutionStreams::STD_ERR) do |line|
1462
1633
  yield nil, nil, line, exec_thread if block_given?
1463
1634
  end
1464
1635
 
1465
1636
  # Handle stdin stream
1466
- input_thread = handle_stream(stream: $stdin, file_type: ExecutionStreams::STD_IN) do |line|
1637
+ input_thread = handle_stream(stream: $stdin,
1638
+ file_type: ExecutionStreams::STD_IN) do |line|
1467
1639
  stdin.puts(line)
1468
1640
  yield line, nil, nil, exec_thread if block_given?
1469
1641
  end
@@ -1490,8 +1662,13 @@ module MarkdownExec
1490
1662
  # @param required_lines [Array<String>] The lines of code to be executed.
1491
1663
  # @param selected [FCB] The selected functional code block object.
1492
1664
  def execute_required_lines(required_lines: [], selected: FCB.new)
1493
- write_command_file(required_lines: required_lines, selected: selected) if @delegate_object[:save_executed_script]
1494
- calc_logged_stdout_filename(block_name: @dml_block_state.block[:oname]) if @dml_block_state
1665
+ if @delegate_object[:save_executed_script]
1666
+ write_command_file(required_lines: required_lines,
1667
+ selected: selected)
1668
+ end
1669
+ if @dml_block_state
1670
+ calc_logged_stdout_filename(block_name: @dml_block_state.block.oname)
1671
+ end
1495
1672
  format_and_execute_command(code_lines: required_lines)
1496
1673
  post_execution_process
1497
1674
  end
@@ -1505,10 +1682,11 @@ module MarkdownExec
1505
1682
  # @param opts [Hash] Options hash containing configuration settings.
1506
1683
  # @param mdoc [YourMDocClass] An instance of the MDoc class.
1507
1684
  #
1508
- def execute_shell_type(selected:, mdoc:, block_source:, link_state: LinkState.new)
1509
- if selected.fetch(:shell, '') == BlockType::LINK
1685
+ def execute_shell_type(selected:, mdoc:, block_source:,
1686
+ link_state: LinkState.new)
1687
+ if selected.shell == BlockType::LINK
1510
1688
  debounce_reset
1511
- push_link_history_and_trigger_load(link_block_body: selected.fetch(:body, ''),
1689
+ push_link_history_and_trigger_load(link_block_body: selected.body,
1512
1690
  mdoc: mdoc,
1513
1691
  selected: selected,
1514
1692
  link_state: link_state,
@@ -1518,46 +1696,34 @@ module MarkdownExec
1518
1696
  debounce_reset
1519
1697
  pop_link_history_and_trigger_load
1520
1698
 
1521
- elsif selected[:shell] == BlockType::OPTS
1699
+ elsif selected.shell == BlockType::OPTS
1522
1700
  debounce_reset
1523
- block_names = []
1524
1701
  code_lines = []
1525
- dependencies = {}
1526
- options_state = read_show_options_and_trigger_reuse(selected: selected, link_state: link_state)
1527
-
1528
- ## apply options to current state
1529
- #
1530
- @menu_base_options.merge!(options_state.options)
1531
- @delegate_object.merge!(options_state.options)
1702
+ options_state = read_show_options_and_trigger_reuse(
1703
+ link_state: link_state,
1704
+ mdoc: @dml_mdoc,
1705
+ selected: selected
1706
+ )
1707
+ update_menu_base(options_state.options)
1532
1708
 
1533
1709
  ### options_state.load_file_link_state
1534
1710
  link_state = LinkState.new
1535
- link_history_push_and_next(
1536
- curr_block_name: selected.pub_name,
1537
- curr_document_filename: @delegate_object[:filename],
1538
- inherited_block_names: ((link_state&.inherited_block_names || []) + block_names).sort.uniq,
1539
- inherited_dependencies: (link_state&.inherited_dependencies || {}).merge(dependencies || {}), ### merge, not replace, key data
1540
- inherited_lines: HashDelegator.code_merge(link_state&.inherited_lines, code_lines),
1541
- next_block_name: '',
1542
- next_document_filename: @delegate_object[:filename],
1543
- next_load_file: LoadFile::REUSE
1544
- )
1711
+ next_state_append_code(selected, link_state, code_lines)
1545
1712
 
1546
- elsif selected[:shell] == BlockType::VARS
1713
+ elsif selected.shell == BlockType::PORT
1547
1714
  debounce_reset
1548
- block_names = []
1549
- code_lines = set_environment_variables_for_block(selected)
1550
- dependencies = {}
1551
- link_history_push_and_next(
1552
- curr_block_name: selected.pub_name,
1553
- curr_document_filename: @delegate_object[:filename],
1554
- inherited_block_names: ((link_state&.inherited_block_names || []) + block_names).sort.uniq,
1555
- inherited_dependencies: (link_state&.inherited_dependencies || {}).merge(dependencies || {}), ### merge, not replace, key data
1556
- inherited_lines: HashDelegator.code_merge(link_state&.inherited_lines, code_lines),
1557
- next_block_name: '',
1558
- next_document_filename: @delegate_object[:filename],
1559
- next_load_file: LoadFile::REUSE
1715
+ required_lines = collect_required_code_lines(
1716
+ mdoc: @dml_mdoc,
1717
+ selected: selected,
1718
+ link_state: link_state,
1719
+ block_source: block_source
1560
1720
  )
1721
+ next_state_set_code(selected, link_state, required_lines)
1722
+
1723
+ elsif selected.shell == BlockType::VARS
1724
+ debounce_reset
1725
+ next_state_append_code(selected, link_state,
1726
+ set_environment_variables_for_block(selected))
1561
1727
 
1562
1728
  elsif debounce_allows
1563
1729
  compile_execute_and_trigger_reuse(mdoc: mdoc,
@@ -1646,6 +1812,16 @@ module MarkdownExec
1646
1812
  expr.include?('%{') ? format_expression(expr) : expr
1647
1813
  end
1648
1814
 
1815
+ def generate_temp_filename(ext = '.sh')
1816
+ filename = begin
1817
+ Dir::Tmpname.make_tmpname(['x', ext], nil)
1818
+ rescue NoMethodError
1819
+ require 'securerandom'
1820
+ "#{SecureRandom.urlsafe_base64}#{ext}"
1821
+ end
1822
+ File.join(Dir.tmpdir, filename)
1823
+ end
1824
+
1649
1825
  # Processes a block to generate its summary, modifying its attributes based on various matching criteria.
1650
1826
  # It handles special formatting for bash blocks, extracting and setting properties like call, stdin, stdout, and dname.
1651
1827
  #
@@ -1659,7 +1835,7 @@ module MarkdownExec
1659
1835
  bm = extract_named_captures_from_option(titlexcall,
1660
1836
  @delegate_object[:block_name_match])
1661
1837
 
1662
- shell_color_option = SHELL_COLOR_OPTIONS[fcb[:shell]]
1838
+ shell_color_option = SHELL_COLOR_OPTIONS[fcb.shell]
1663
1839
 
1664
1840
  if @delegate_object[:block_name_nick_match].present? && fcb.oname =~ Regexp.new(@delegate_object[:block_name_nick_match])
1665
1841
  fcb.nickname = $~[0]
@@ -1670,9 +1846,10 @@ module MarkdownExec
1670
1846
 
1671
1847
  fcb.dname = HashDelegator.indent_all_lines(
1672
1848
  apply_shell_color_option(fcb.oname, shell_color_option),
1673
- fcb.fetch(:indent, nil)
1849
+ fcb.indent
1674
1850
  )
1675
- fcb
1851
+
1852
+ fcb # &br
1676
1853
  end
1677
1854
 
1678
1855
  # Updates the delegate object's state based on the provided block state.
@@ -1695,13 +1872,13 @@ module MarkdownExec
1695
1872
  Thread.new do
1696
1873
  stream.each_line do |line|
1697
1874
  line.strip!
1698
- @run_state.files.append_stream_line(file_type, line) if @run_state.files.streams
1699
-
1700
- if @delegate_object[:output_stdout]
1701
- # print line
1702
- puts line
1875
+ if @run_state.files.streams
1876
+ @run_state.files.append_stream_line(file_type,
1877
+ line)
1703
1878
  end
1704
1879
 
1880
+ puts line if @delegate_object[:output_stdout]
1881
+
1705
1882
  yield line if block_given?
1706
1883
  end
1707
1884
  rescue IOError
@@ -1712,25 +1889,40 @@ module MarkdownExec
1712
1889
  end
1713
1890
  end
1714
1891
 
1715
- def history_files
1716
- Dir.glob(
1892
+ def history_files(link_state, order: :chronological, direction: :reverse)
1893
+ files = Dir.glob(
1717
1894
  File.join(
1718
1895
  @delegate_object[:saved_script_folder],
1719
- SavedAsset.new(filename: @delegate_object[:filename],
1720
- saved_asset_format: @delegate_object[:saved_asset_format]).generate_name
1896
+ SavedAsset.new(
1897
+ filename: @delegate_object[:filename],
1898
+ saved_asset_format: shell_escape_asset_format(link_state)
1899
+ ).generate_name
1721
1900
  )
1722
1901
  )
1902
+
1903
+ sorted_files = case order
1904
+ when :alphabetical
1905
+ files.sort
1906
+ when :chronological
1907
+ files.sort_by { |file| File.mtime(file) }
1908
+ else
1909
+ raise ArgumentError, "Invalid order: #{order}"
1910
+ end
1911
+
1912
+ direction == :reverse ? sorted_files.reverse : sorted_files
1723
1913
  end
1724
1914
 
1725
1915
  # Initializes variables for regex and other states
1726
1916
  def initial_state
1727
1917
  {
1728
- fenced_start_and_end_regex: Regexp.new(@delegate_object.fetch(
1729
- :fenced_start_and_end_regex, '^(?<indent> *)`{3,}'
1730
- )),
1731
- fenced_start_extended_regex: Regexp.new(@delegate_object.fetch(
1732
- :fenced_start_and_end_regex, '^(?<indent> *)`{3,}'
1733
- )),
1918
+ fenced_start_and_end_regex:
1919
+ Regexp.new(@delegate_object.fetch(
1920
+ :fenced_start_and_end_regex, '^(?<indent> *)`{3,}'
1921
+ )),
1922
+ fenced_start_extended_regex:
1923
+ Regexp.new(@delegate_object.fetch(
1924
+ :fenced_start_and_end_regex, '^(?<indent> *)`{3,}'
1925
+ )),
1734
1926
  fcb: MarkdownExec::FCB.new,
1735
1927
  in_fenced_block: false,
1736
1928
  headings: []
@@ -1740,7 +1932,7 @@ module MarkdownExec
1740
1932
  def inpseq_execute_block(block_name)
1741
1933
  @dml_block_state = block_state_for_name_from_cli(block_name)
1742
1934
  dump_and_warn_block_state(selected: @dml_block_state.block)
1743
- @dml_link_state, @dml_menu_default_dname = \
1935
+ @dml_link_state, @dml_menu_default_dname =
1744
1936
  exec_bash_next_state(
1745
1937
  selected: @dml_block_state.block,
1746
1938
  mdoc: @dml_mdoc,
@@ -1757,8 +1949,8 @@ module MarkdownExec
1757
1949
  @run_state.in_own_window = false
1758
1950
 
1759
1951
  # &bsp 'loop', block_name_from_cli, @cli_block_name
1760
- @run_state.block_name_from_cli, @dml_now_using_cli, @dml_blocks_in_file, @dml_menu_blocks, @dml_mdoc = \
1761
- set_delobj_menu_loop_vars(block_name_from_cli: @run_state.block_name_from_cli,
1952
+ @run_state.source.block_name_from_cli, @dml_now_using_cli, @dml_blocks_in_file, @dml_menu_blocks, @dml_mdoc =
1953
+ set_delobj_menu_loop_vars(block_name_from_cli: @run_state.source.block_name_from_cli,
1762
1954
  now_using_cli: @dml_now_using_cli,
1763
1955
  link_state: @dml_link_state)
1764
1956
  end
@@ -1767,7 +1959,7 @@ module MarkdownExec
1767
1959
  @dml_block_state = load_cli_or_user_selected_block(all_blocks: @dml_blocks_in_file,
1768
1960
  menu_blocks: @dml_menu_blocks,
1769
1961
  default: @dml_menu_default_dname)
1770
- # &bsp '@run_state.block_name_from_cli:',@run_state.block_name_from_cli
1962
+ # &bsp '@run_state.source.block_name_from_cli:',@run_state.source.block_name_from_cli
1771
1963
  if !@dml_block_state
1772
1964
  HashDelegator.error_handler('block_state missing', { abort: true })
1773
1965
  elsif @dml_block_state.state == MenuState::EXIT
@@ -1793,8 +1985,10 @@ module MarkdownExec
1793
1985
  end
1794
1986
  end
1795
1987
 
1796
- def link_block_data_eval(link_state, code_lines, selected, link_block_data, block_source:)
1797
- all_code = HashDelegator.code_merge(link_state&.inherited_lines, code_lines)
1988
+ def link_block_data_eval(link_state, code_lines, selected, link_block_data,
1989
+ block_source:)
1990
+ all_code = HashDelegator.code_merge(link_state&.inherited_lines,
1991
+ code_lines)
1798
1992
  output_lines = []
1799
1993
 
1800
1994
  Tempfile.open do |file|
@@ -1813,7 +2007,8 @@ module MarkdownExec
1813
2007
  #
1814
2008
  output_lines = process_string_array(
1815
2009
  output_lines,
1816
- begin_pattern: @delegate_object.fetch(:output_assignment_begin, nil),
2010
+ begin_pattern: @delegate_object.fetch(:output_assignment_begin,
2011
+ nil),
1817
2012
  end_pattern: @delegate_object.fetch(:output_assignment_end, nil),
1818
2013
  scan1: @delegate_object.fetch(:output_assignment_match, nil),
1819
2014
  format1: @delegate_object.fetch(:output_assignment_format, nil)
@@ -1824,7 +2019,10 @@ module MarkdownExec
1824
2019
  end
1825
2020
  end
1826
2021
 
1827
- HashDelegator.error_handler('all_code eval output_lines is nil', { abort: true }) unless output_lines
2022
+ unless output_lines
2023
+ HashDelegator.error_handler('all_code eval output_lines is nil',
2024
+ { abort: true })
2025
+ end
1828
2026
 
1829
2027
  label_format_above = @delegate_object[:shell_code_label_format_above]
1830
2028
  label_format_below = @delegate_object[:shell_code_label_format_below]
@@ -1833,7 +2031,11 @@ module MarkdownExec
1833
2031
  block_source.merge({ block_name: selected.pub_name }))] +
1834
2032
  output_lines.map do |line|
1835
2033
  re = Regexp.new(link_block_data.fetch('pattern', '(?<line>.*)'))
1836
- re.gsub_format(line, link_block_data.fetch('format', '%{line}')) if re =~ line
2034
+ next unless re =~ line
2035
+
2036
+ re.gsub_format(line,
2037
+ link_block_data.fetch('format',
2038
+ '%{line}'))
1837
2039
  end.compact +
1838
2040
  [label_format_below && format(label_format_below,
1839
2041
  block_source.merge({ block_name: selected.pub_name }))]
@@ -1882,34 +2084,41 @@ module MarkdownExec
1882
2084
  # Executes a specified block once per filename.
1883
2085
  # @param all_blocks [Array] Array of all block elements.
1884
2086
  # @return [Boolean, nil] True if values were modified, nil otherwise.
1885
- def load_auto_opts_block(all_blocks)
2087
+ def load_auto_opts_block(all_blocks, mdoc:)
1886
2088
  block_name = @delegate_object[:document_load_opts_block_name]
1887
- return unless block_name.present? && @most_recent_loaded_filename != @delegate_object[:filename]
2089
+ unless block_name.present? && @most_recent_loaded_filename != @delegate_object[:filename]
2090
+ return
2091
+ end
1888
2092
 
1889
2093
  block = HashDelegator.block_find(all_blocks, :oname, block_name)
1890
2094
  return unless block
1891
2095
 
1892
- options_state = read_show_options_and_trigger_reuse(selected: block)
1893
- @menu_base_options.merge!(options_state.options)
1894
- @delegate_object.merge!(options_state.options)
2096
+ options_state = read_show_options_and_trigger_reuse(
2097
+ mdoc: mdoc,
2098
+ selected: block
2099
+ )
2100
+ update_menu_base(options_state.options)
1895
2101
 
1896
2102
  @most_recent_loaded_filename = @delegate_object[:filename]
1897
2103
  true
1898
2104
  end
1899
2105
 
1900
- def load_cli_or_user_selected_block(all_blocks: [], menu_blocks: [], default: nil)
2106
+ def load_cli_or_user_selected_block(all_blocks: [], menu_blocks: [],
2107
+ default: nil)
1901
2108
  if @delegate_object[:block_name].present?
1902
2109
  block = all_blocks.find do |item|
1903
2110
  item.pub_name == @delegate_object[:block_name]
1904
- end&.merge(block_name_from_ui: false)
2111
+ end
2112
+ source = OpenStruct.new(block_name_from_ui: false)
1905
2113
  else
1906
2114
  block_state = wait_for_user_selected_block(all_blocks, menu_blocks,
1907
2115
  default)
1908
- block = block_state.block&.merge(block_name_from_ui: true)
2116
+ block = block_state.block
2117
+ source = OpenStruct.new(block_name_from_ui: true)
1909
2118
  state = block_state.state
1910
2119
  end
1911
2120
 
1912
- SelectedBlockMenuState.new(block, state)
2121
+ SelectedBlockMenuState.new(block, source, state)
1913
2122
  rescue StandardError
1914
2123
  HashDelegator.error_handler('load_cli_or_user_selected_block')
1915
2124
  end
@@ -1932,7 +2141,8 @@ module MarkdownExec
1932
2141
  def load_filespec_wildcard_expansion(expr, auto_load_single: false)
1933
2142
  files = find_files(expr)
1934
2143
  if files.count.zero?
1935
- HashDelegator.error_handler("no files found with '#{expr}' ", { abort: true })
2144
+ HashDelegator.error_handler("no files found with '#{expr}' ",
2145
+ { abort: true })
1936
2146
  elsif auto_load_single && files.count == 1
1937
2147
  files.first
1938
2148
  else
@@ -1966,20 +2176,22 @@ module MarkdownExec
1966
2176
 
1967
2177
  # recreate menu with new options
1968
2178
  #
1969
- all_blocks, mdoc = mdoc_and_blocks_from_nested_files if load_auto_opts_block(all_blocks)
2179
+ all_blocks, mdoc = mdoc_and_blocks_from_nested_files if load_auto_opts_block(
2180
+ all_blocks, mdoc: mdoc
2181
+ )
1970
2182
 
1971
2183
  menu_blocks = mdoc.fcbs_per_options(@delegate_object)
1972
2184
  add_menu_chrome_blocks!(menu_blocks: menu_blocks, link_state: link_state)
1973
2185
  ### compress empty lines
1974
2186
  HashDelegator.delete_consecutive_blank_lines!(menu_blocks)
1975
- [all_blocks, menu_blocks, mdoc]
2187
+ [all_blocks, menu_blocks, mdoc] # &br
1976
2188
  end
1977
2189
 
1978
2190
  def menu_add_disabled_option(name)
1979
2191
  raise unless name.present?
1980
2192
  raise if @dml_menu_blocks.nil?
1981
2193
 
1982
- block = @dml_menu_blocks.find { |item| item[:oname] == name }
2194
+ block = @dml_menu_blocks.find { |item| item.oname == name }
1983
2195
 
1984
2196
  # create menu item when it is needed (count > 0)
1985
2197
  #
@@ -2017,7 +2229,9 @@ module MarkdownExec
2017
2229
  # @param option_symbol [Symbol] The symbol key for the menu option in the delegate object.
2018
2230
  # @return [String] The formatted or original value of the menu option.
2019
2231
  def menu_chrome_formatted_option(option_symbol = :menu_option_back_name)
2020
- option_value = HashDelegator.safeval(@delegate_object.fetch(option_symbol, ''))
2232
+ option_value = HashDelegator.safeval(@delegate_object.fetch(
2233
+ option_symbol, ''
2234
+ ))
2021
2235
 
2022
2236
  if @delegate_object[:menu_chrome_format]
2023
2237
  format(@delegate_object[:menu_chrome_format], option_value)
@@ -2030,20 +2244,20 @@ module MarkdownExec
2030
2244
  raise unless name.present?
2031
2245
  raise if @dml_menu_blocks.nil?
2032
2246
 
2033
- item = @dml_menu_blocks.find { |block| block[:oname] == name }
2247
+ item = @dml_menu_blocks.find { |block| block.oname == name }
2034
2248
 
2035
2249
  # create menu item when it is needed (count > 0)
2036
2250
  #
2037
2251
  if item.nil? && count.positive?
2038
- append_chrome_block(menu_blocks: @dml_menu_blocks, menu_state: menu_state)
2039
- item = @dml_menu_blocks.find { |block| block[:oname] == name }
2252
+ item = append_chrome_block(menu_blocks: @dml_menu_blocks,
2253
+ menu_state: menu_state)
2040
2254
  end
2041
2255
 
2042
2256
  # update item if it exists
2043
2257
  #
2044
2258
  return unless item
2045
2259
 
2046
- item[:dname] = type.present? ? "#{name} (#{count} #{type})" : name
2260
+ item.dname = type.present? ? "#{name} (#{count} #{type})" : name
2047
2261
  if count.positive?
2048
2262
  item.delete(:disabled)
2049
2263
  else
@@ -2051,14 +2265,15 @@ module MarkdownExec
2051
2265
  end
2052
2266
  end
2053
2267
 
2054
- def manage_cli_selection_state(block_name_from_cli:, now_using_cli:, link_state:)
2268
+ def manage_cli_selection_state(block_name_from_cli:, now_using_cli:,
2269
+ link_state:)
2055
2270
  if block_name_from_cli && @cli_block_name == @menu_base_options[:menu_persist_block_name]
2056
2271
  # &bsp 'pause cli control, allow user to select block'
2057
2272
  block_name_from_cli = false
2058
2273
  now_using_cli = false
2059
- @menu_base_options[:block_name] = \
2274
+ @menu_base_options[:block_name] =
2060
2275
  @delegate_object[:block_name] = \
2061
- link_state.block_name = \
2276
+ link_state.block_name =
2062
2277
  @cli_block_name = nil
2063
2278
  end
2064
2279
 
@@ -2079,6 +2294,27 @@ module MarkdownExec
2079
2294
  end
2080
2295
  end
2081
2296
 
2297
+ def next_state_append_code(selected, link_state, code_lines)
2298
+ next_state_set_code(selected, link_state, HashDelegator.code_merge(
2299
+ link_state&.inherited_lines, code_lines
2300
+ ))
2301
+ end
2302
+
2303
+ def next_state_set_code(selected, link_state, code_lines)
2304
+ block_names = []
2305
+ dependencies = {}
2306
+ link_history_push_and_next(
2307
+ curr_block_name: selected.pub_name,
2308
+ curr_document_filename: @delegate_object[:filename],
2309
+ inherited_block_names: ((link_state&.inherited_block_names || []) + block_names).sort.uniq,
2310
+ inherited_dependencies: (link_state&.inherited_dependencies || {}).merge(dependencies || {}), ### merge, not replace, key data
2311
+ inherited_lines: HashDelegator.code_merge(code_lines),
2312
+ next_block_name: '',
2313
+ next_document_filename: @delegate_object[:filename],
2314
+ next_load_file: LoadFile::REUSE
2315
+ )
2316
+ end
2317
+
2082
2318
  def output_color_formatted(data_sym, color_sym)
2083
2319
  formatted_string = string_send_color(@delegate_object[data_sym],
2084
2320
  color_sym)
@@ -2159,9 +2395,12 @@ module MarkdownExec
2159
2395
  link_history_push_and_next(
2160
2396
  curr_block_name: selected.pub_name,
2161
2397
  curr_document_filename: @delegate_object[:filename],
2162
- inherited_block_names: ((link_state&.inherited_block_names || []) + block_names).sort.uniq,
2163
- inherited_dependencies: (link_state&.inherited_dependencies || {}).merge(dependencies || {}), ### merge, not replace, key data
2164
- inherited_lines: HashDelegator.code_merge(link_state&.inherited_lines, code_lines),
2398
+ inherited_block_names:
2399
+ ((link_state&.inherited_block_names || []) + block_names).sort.uniq,
2400
+ inherited_dependencies:
2401
+ (link_state&.inherited_dependencies || {}).merge(dependencies || {}), ### merge, not replace, key data
2402
+ inherited_lines:
2403
+ HashDelegator.code_merge(link_state&.inherited_lines, code_lines),
2165
2404
  next_block_name: next_block_name,
2166
2405
  next_document_filename: @delegate_object[:filename], # not next_document_filename
2167
2406
  next_load_file: LoadFile::REUSE # not next_document_filename == @delegate_object[:filename] ? LoadFile::REUSE : LoadFile::LOAD
@@ -2177,12 +2416,15 @@ module MarkdownExec
2177
2416
  def pop_link_history_and_trigger_load
2178
2417
  pop = @link_history.pop
2179
2418
  peek = @link_history.peek
2180
- LoadFileLinkState.new(LoadFile::LOAD, LinkState.new(
2181
- document_filename: pop.document_filename,
2182
- inherited_block_names: peek.inherited_block_names,
2183
- inherited_dependencies: peek.inherited_dependencies,
2184
- inherited_lines: peek.inherited_lines
2185
- ))
2419
+ LoadFileLinkState.new(
2420
+ LoadFile::LOAD,
2421
+ LinkState.new(
2422
+ document_filename: pop.document_filename,
2423
+ inherited_block_names: peek.inherited_block_names,
2424
+ inherited_dependencies: peek.inherited_dependencies,
2425
+ inherited_lines: peek.inherited_lines
2426
+ )
2427
+ )
2186
2428
  end
2187
2429
 
2188
2430
  def post_execution_process
@@ -2198,20 +2440,21 @@ module MarkdownExec
2198
2440
  # @return [Array<Hash>] The updated blocks menu.
2199
2441
  def prepare_blocks_menu(menu_blocks)
2200
2442
  menu_blocks.map do |fcb|
2201
- next if Filter.prepared_not_in_menu?(@delegate_object, fcb,
2202
- %i[block_name_include_match block_name_wrapper_match])
2443
+ next if Filter.prepared_not_in_menu?(
2444
+ @delegate_object,
2445
+ fcb,
2446
+ %i[block_name_include_match block_name_wrapper_match]
2447
+ )
2203
2448
 
2204
- fcb.merge!(
2205
- name: fcb.dname,
2206
- label: BlockLabel.make(
2207
- body: fcb[:body],
2208
- filename: @delegate_object[:filename],
2209
- headings: fcb.fetch(:headings, []),
2210
- menu_blocks_with_docname: @delegate_object[:menu_blocks_with_docname],
2211
- menu_blocks_with_headings: @delegate_object[:menu_blocks_with_headings],
2212
- text: fcb[:text],
2213
- title: fcb[:title]
2214
- )
2449
+ fcb.name = fcb.dname
2450
+ fcb.label = BlockLabel.make(
2451
+ body: fcb.body,
2452
+ filename: @delegate_object[:filename],
2453
+ headings: fcb.headings,
2454
+ menu_blocks_with_docname: @delegate_object[:menu_blocks_with_docname],
2455
+ menu_blocks_with_headings: @delegate_object[:menu_blocks_with_headings],
2456
+ text: fcb.text,
2457
+ title: fcb.title
2215
2458
  )
2216
2459
  fcb.to_h
2217
2460
  end.compact
@@ -2232,7 +2475,9 @@ module MarkdownExec
2232
2475
  when :filter
2233
2476
  %i[blocks line]
2234
2477
  when :line
2235
- create_and_add_chrome_blocks(blocks, fcb) unless @delegate_object[:no_chrome]
2478
+ unless @delegate_object[:no_chrome]
2479
+ create_and_add_chrome_blocks(blocks, fcb)
2480
+ end
2236
2481
  end
2237
2482
  end
2238
2483
 
@@ -2305,7 +2550,8 @@ module MarkdownExec
2305
2550
  # @param filespec [String] the wildcard expression to be substituted
2306
2551
  # @return [String, nil] the resolved path or substituted expression, or nil if interrupted
2307
2552
  def prompt_for_filespec_with_wildcard(filespec)
2308
- puts format(@delegate_object[:prompt_show_expr_format], { expr: filespec })
2553
+ puts format(@delegate_object[:prompt_show_expr_format],
2554
+ { expr: filespec })
2309
2555
  puts @delegate_object[:prompt_enter_filespec]
2310
2556
 
2311
2557
  begin
@@ -2362,26 +2608,40 @@ module MarkdownExec
2362
2608
 
2363
2609
  # public
2364
2610
 
2365
- def prompt_select_code_filename(filenames, string: @delegate_object[:prompt_select_code_file], color_sym: :prompt_color_after_script_execution)
2611
+ def prompt_select_code_filename(
2612
+ filenames,
2613
+ color_sym: :prompt_color_after_script_execution,
2614
+ cycle: true,
2615
+ enum: false,
2616
+ quiet: true,
2617
+ string: @delegate_object[:prompt_select_code_file]
2618
+ )
2366
2619
  @prompt.select(
2367
2620
  string_send_color(string, color_sym),
2368
- filter: true,
2369
- quiet: true
2621
+ cycle: cycle,
2622
+ filter: !enum,
2623
+ per_page: @delegate_object[:select_page_height],
2624
+ quiet: quiet
2370
2625
  ) do |menu|
2371
- filenames.each do |filename|
2372
- menu.choice filename
2626
+ menu.enum '.' if enum
2627
+ filenames.each.with_index do |filename, ind|
2628
+ if enum
2629
+ menu.choice filename, ind + 1
2630
+ else
2631
+ menu.choice filename
2632
+ end
2373
2633
  end
2374
2634
  end
2375
2635
  rescue TTY::Reader::InputInterrupt
2376
2636
  exit 1
2377
2637
  end
2378
2638
 
2379
- def prompt_select_continue
2639
+ def prompt_select_continue(filter: true, quiet: true)
2380
2640
  sel = @prompt.select(
2381
2641
  string_send_color(@delegate_object[:prompt_after_script_execution],
2382
2642
  :prompt_color_after_script_execution),
2383
- filter: true,
2384
- quiet: true
2643
+ filter: filter,
2644
+ quiet: quiet
2385
2645
  ) do |menu|
2386
2646
  menu.choice @delegate_object[:prompt_yes]
2387
2647
  menu.choice @delegate_object[:prompt_exit]
@@ -2394,7 +2654,7 @@ module MarkdownExec
2394
2654
  # user prompt to exit if the menu will be displayed again
2395
2655
  #
2396
2656
  def prompt_user_exit(block_name_from_cli:, selected:)
2397
- selected[:shell] == BlockType::BASH &&
2657
+ selected.shell == BlockType::BASH &&
2398
2658
  @delegate_object[:pause_after_script_execution] &&
2399
2659
  prompt_select_continue == MenuState::EXIT
2400
2660
  end
@@ -2443,18 +2703,26 @@ module MarkdownExec
2443
2703
  #
2444
2704
  if (load_expr = link_block_data.fetch(LinkKeys::LOAD, '')).present?
2445
2705
  load_filespec = load_filespec_from_expression(load_expr)
2446
- code_lines += File.readlines(load_filespec, chomp: true) if load_filespec
2706
+ if load_filespec
2707
+ code_lines += File.readlines(load_filespec,
2708
+ chomp: true)
2709
+ end
2447
2710
  end
2448
2711
 
2449
2712
  # if an eval link block, evaluate code_lines and return its standard output
2450
2713
  #
2451
2714
  if link_block_data.fetch(LinkKeys::EVAL,
2452
- false) || link_block_data.fetch(LinkKeys::EXEC, false)
2453
- code_lines = link_block_data_eval(link_state, code_lines, selected, link_block_data, block_source: block_source)
2715
+ false) || link_block_data.fetch(LinkKeys::EXEC,
2716
+ false)
2717
+ code_lines = link_block_data_eval(link_state, code_lines, selected, link_block_data,
2718
+ block_source: block_source)
2454
2719
  end
2455
2720
 
2456
- next_document_filename = write_inherited_lines_to_file(link_state, link_block_data)
2457
- next_block_name = link_block_data.fetch(LinkKeys::NEXT_BLOCK, nil) || link_block_data.fetch(LinkKeys::BLOCK, nil) || ''
2721
+ next_document_filename = write_inherited_lines_to_file(link_state,
2722
+ link_block_data)
2723
+ next_block_name = link_block_data.fetch(LinkKeys::NEXT_BLOCK,
2724
+ nil) || link_block_data.fetch(LinkKeys::BLOCK,
2725
+ nil) || ''
2458
2726
 
2459
2727
  if link_block_data[LinkKeys::RETURN]
2460
2728
  pop_add_current_code_to_head_and_trigger_load(link_state, block_names, code_lines,
@@ -2466,7 +2734,9 @@ module MarkdownExec
2466
2734
  curr_document_filename: @delegate_object[:filename],
2467
2735
  inherited_block_names: ((link_state&.inherited_block_names || []) + block_names).sort.uniq,
2468
2736
  inherited_dependencies: (link_state&.inherited_dependencies || {}).merge(dependencies || {}), ### merge, not replace, key data
2469
- inherited_lines: HashDelegator.code_merge(link_state&.inherited_lines, code_lines),
2737
+ inherited_lines: HashDelegator.code_merge(
2738
+ link_state&.inherited_lines, code_lines
2739
+ ),
2470
2740
  next_block_name: next_block_name,
2471
2741
  next_document_filename: next_document_filename,
2472
2742
  next_load_file: next_document_filename == @delegate_object[:filename] ? LoadFile::REUSE : LoadFile::LOAD
@@ -2487,14 +2757,29 @@ module MarkdownExec
2487
2757
  # @param selected [Hash] Selected item from the menu containing a YAML body.
2488
2758
  # @param tgt2 [Hash, nil] An optional target hash to update with YAML data.
2489
2759
  # @return [LoadFileLinkState] An instance indicating the next action for loading files.
2490
- def read_show_options_and_trigger_reuse(selected:, link_state: LinkState.new)
2760
+ def read_show_options_and_trigger_reuse(selected:,
2761
+ mdoc:, link_state: LinkState.new)
2491
2762
  obj = {}
2492
- data = YAML.load(selected[:body].join("\n"))
2493
- (data || []).each do |key, value|
2494
- sym_key = key.to_sym
2495
- obj[sym_key] = value
2496
2763
 
2497
- print_formatted_option(key, value) if @delegate_object[:menu_opts_set_format].present?
2764
+ # concatenated body of all required blocks loaded a YAML
2765
+ data = YAML.load(
2766
+ collect_required_code_lines(
2767
+ mdoc: mdoc, selected: selected,
2768
+ link_state: link_state, block_source: {}
2769
+ ).join("\n")
2770
+ ).transform_keys(&:to_sym)
2771
+
2772
+ if selected.shell == BlockType::OPTS
2773
+ obj = data
2774
+ else
2775
+ (data || []).each do |key, value|
2776
+ sym_key = key.to_sym
2777
+ obj[sym_key] = value
2778
+
2779
+ if @delegate_object[:menu_opts_set_format].present?
2780
+ print_formatted_option(key, value)
2781
+ end
2782
+ end
2498
2783
  end
2499
2784
 
2500
2785
  link_state.block_name = nil
@@ -2526,11 +2811,19 @@ module MarkdownExec
2526
2811
  resize_terminal
2527
2812
  end
2528
2813
 
2529
- opts[:console_height], opts[:console_width] = opts[:console_winsize] = IO.console.winsize if resized || !opts[:console_width]
2814
+ if resized || !opts[:console_width]
2815
+ opts[:console_height], opts[:console_width] = opts[:console_winsize] =
2816
+ IO.console.winsize
2817
+ end
2530
2818
 
2531
- opts[:per_page] = opts[:select_page_height] = [opts[:console_height] - 3, 4].max unless opts[:select_page_height]&.positive?
2819
+ unless opts[:select_page_height]&.positive?
2820
+ opts[:per_page] =
2821
+ opts[:select_page_height] =
2822
+ [opts[:console_height] - 3, 4].max
2823
+ end
2532
2824
  rescue StandardError
2533
- HashDelegator.error_handler('register_console_attributes', { abort: true })
2825
+ HashDelegator.error_handler('register_console_attributes',
2826
+ { abort: true })
2534
2827
  end
2535
2828
 
2536
2829
  # Check if the delegate object responds to a given method.
@@ -2542,7 +2835,8 @@ module MarkdownExec
2542
2835
  true
2543
2836
  elsif @delegate_object.respond_to?(method_name, include_private)
2544
2837
  true
2545
- elsif method_name.to_s.end_with?('=') && @delegate_object.respond_to?(:[]=, include_private)
2838
+ elsif method_name.to_s.end_with?('=') && @delegate_object.respond_to?(:[]=,
2839
+ include_private)
2546
2840
  true
2547
2841
  else
2548
2842
  @delegate_object.respond_to?(method_name, include_private)
@@ -2593,7 +2887,8 @@ module MarkdownExec
2593
2887
  # input into path with wildcard for easy entry
2594
2888
  #
2595
2889
  case (name = prompt_select_code_filename(
2596
- [@delegate_object[:prompt_filespec_back], @delegate_object[:prompt_filespec_other]] + files,
2890
+ [@delegate_object[:prompt_filespec_back],
2891
+ @delegate_object[:prompt_filespec_other]] + files,
2597
2892
  string: @delegate_object[:prompt_select_code_file],
2598
2893
  color_sym: :prompt_color_after_script_execution
2599
2894
  ))
@@ -2613,37 +2908,46 @@ module MarkdownExec
2613
2908
  end
2614
2909
 
2615
2910
  # Presents a TTY prompt to select an option or exit, returns metadata including option and selected
2616
- def select_option_with_metadata(prompt_text, names, opts = {})
2911
+ def select_option_with_metadata(prompt_text, menu_items, opts = {})
2617
2912
  ## configure to environment
2618
2913
  #
2619
2914
  register_console_attributes(opts)
2620
2915
 
2621
2916
  # crashes if all menu options are disabled
2622
2917
  selection = @prompt.select(prompt_text,
2623
- names,
2918
+ menu_items,
2624
2919
  opts.merge(filter: true))
2625
- selected_name = names.find do |item|
2920
+
2921
+ selected = menu_items.find do |item|
2626
2922
  if item.instance_of?(Hash)
2627
- item[:dname] == selection
2923
+ (item[:name] || item[:dname]) == selection
2924
+ elsif item.instance_of?(MarkdownExec::FCB)
2925
+ item.dname == selection
2628
2926
  else
2629
2927
  item == selection
2630
2928
  end
2631
2929
  end
2632
- selected_name = { dname: selected_name } if selected_name.instance_of?(String)
2633
- unless selected_name
2634
- HashDelegator.error_handler('select_option_with_metadata', error: 'menu item not found')
2930
+ if selected.instance_of?(String)
2931
+ selected = FCB.new(dname: selected)
2932
+ elsif selected.instance_of?(Hash)
2933
+ selected = FCB.new(selected)
2934
+ end
2935
+ unless selected
2936
+ HashDelegator.error_handler('select_option_with_metadata',
2937
+ error: 'menu item not found')
2635
2938
  exit 1
2636
2939
  end
2637
2940
 
2638
- selected_name.merge(
2639
- if selection == menu_chrome_colored_option(:menu_option_back_name)
2640
- { option: selection, shell: BlockType::LINK }
2641
- elsif selection == menu_chrome_colored_option(:menu_option_exit_name)
2642
- { option: selection }
2643
- else
2644
- { selected: selection }
2645
- end
2646
- )
2941
+ if selection == menu_chrome_colored_option(:menu_option_back_name)
2942
+ selected.option = selection
2943
+ selected.shell = BlockType::LINK
2944
+ elsif selection == menu_chrome_colored_option(:menu_option_exit_name)
2945
+ selected.option = selection
2946
+ else
2947
+ selected.selected = selection
2948
+ end
2949
+
2950
+ selected
2647
2951
  rescue TTY::Reader::InputInterrupt
2648
2952
  exit 1
2649
2953
  rescue StandardError
@@ -2663,8 +2967,9 @@ module MarkdownExec
2663
2967
  block_name_from_cli ? @cli_block_name : link_state.block_name
2664
2968
  end
2665
2969
 
2666
- def set_delobj_menu_loop_vars(block_name_from_cli:, now_using_cli:, link_state:)
2667
- block_name_from_cli, now_using_cli = \
2970
+ def set_delobj_menu_loop_vars(block_name_from_cli:, now_using_cli:,
2971
+ link_state:)
2972
+ block_name_from_cli, now_using_cli =
2668
2973
  manage_cli_selection_state(block_name_from_cli: block_name_from_cli,
2669
2974
  now_using_cli: now_using_cli,
2670
2975
  link_state: link_state)
@@ -2681,7 +2986,7 @@ module MarkdownExec
2681
2986
 
2682
2987
  def set_environment_variables_for_block(selected)
2683
2988
  code_lines = []
2684
- YAML.load(selected[:body].join("\n"))&.each do |key, value|
2989
+ YAML.load(selected.body.join("\n"))&.each do |key, value|
2685
2990
  ENV[key] = value.to_s
2686
2991
 
2687
2992
  require 'shellwords'
@@ -2696,6 +3001,29 @@ module MarkdownExec
2696
3001
  code_lines
2697
3002
  end
2698
3003
 
3004
+ def shell_escape_asset_format(link_state)
3005
+ raw = @delegate_object[:saved_asset_format]
3006
+
3007
+ return raw unless @delegate_object[:shell_parameter_expansion]
3008
+
3009
+ # unchanged if no parameter expansion takes place
3010
+ return raw unless /$/ =~ raw
3011
+
3012
+ filespec = generate_temp_filename
3013
+ cmd = [@delegate_object[:shell], '-c', filespec].join(' ')
3014
+
3015
+ marker = Random.new.rand.to_s
3016
+
3017
+ code = (link_state&.inherited_lines || []) + ["echo -n \"#{marker}#{raw}\""]
3018
+ # &bt code
3019
+ File.write filespec, HashDelegator.join_code_lines(code)
3020
+ File.chmod 0o755, filespec
3021
+
3022
+ out = `#{cmd}`.sub(/.*?#{marker}/m, '')
3023
+ File.delete filespec
3024
+ out # &br
3025
+ end
3026
+
2699
3027
  def should_add_back_option?
2700
3028
  @delegate_object[:menu_with_back] && @link_history.prior_state_exist?
2701
3029
  end
@@ -2818,6 +3146,13 @@ module MarkdownExec
2818
3146
  end
2819
3147
  end
2820
3148
 
3149
+ ## apply options to current state
3150
+ #
3151
+ def update_menu_base(options)
3152
+ @menu_base_options.merge!(options)
3153
+ @delegate_object.merge!(options)
3154
+ end
3155
+
2821
3156
  def wait_for_stream_processing
2822
3157
  @process_mutex.synchronize do
2823
3158
  @process_cv.wait(@process_mutex)
@@ -2839,8 +3174,11 @@ module MarkdownExec
2839
3174
  @delegate_object[:prompt_select_block].to_s, :prompt_color_after_script_execution
2840
3175
  )
2841
3176
 
2842
- block_menu = prepare_blocks_menu(menu_blocks)
2843
- return SelectedBlockMenuState.new(nil, MenuState::EXIT) if block_menu.empty?
3177
+ menu_items = prepare_blocks_menu(menu_blocks)
3178
+ if menu_items.empty?
3179
+ return SelectedBlockMenuState.new(nil, OpenStruct.new,
3180
+ MenuState::EXIT)
3181
+ end
2844
3182
 
2845
3183
  # default value may not match if color is different from originating menu (opts changed while processing)
2846
3184
  selection_opts = if default && menu_blocks.map(&:dname).include?(default)
@@ -2852,7 +3190,7 @@ module MarkdownExec
2852
3190
  sph = @delegate_object[:select_page_height]
2853
3191
  selection_opts.merge!(per_page: sph)
2854
3192
 
2855
- selected_option = select_option_with_metadata(prompt_title, block_menu,
3193
+ selected_option = select_option_with_metadata(prompt_title, menu_items,
2856
3194
  selection_opts)
2857
3195
  determine_block_state(selected_option)
2858
3196
  end
@@ -2867,7 +3205,7 @@ module MarkdownExec
2867
3205
  exts: '.sh',
2868
3206
  filename: @delegate_object[:filename],
2869
3207
  prefix: @delegate_object[:saved_script_filename_prefix],
2870
- saved_asset_format: @delegate_object[:saved_asset_format],
3208
+ saved_asset_format: shell_escape_asset_format(@dml_link_state),
2871
3209
  time: time_now).generate_name
2872
3210
  @run_state.saved_filespec =
2873
3211
  File.join(@delegate_object[:saved_script_folder],
@@ -2918,7 +3256,8 @@ module MarkdownExec
2918
3256
  save_expr = link_block_data.fetch(LinkKeys::SAVE, '')
2919
3257
  if save_expr.present?
2920
3258
  save_filespec = save_filespec_from_expression(save_expr)
2921
- File.write(save_filespec, HashDelegator.join_code_lines(link_state&.inherited_lines))
3259
+ File.write(save_filespec,
3260
+ HashDelegator.join_code_lines(link_state&.inherited_lines))
2922
3261
  @delegate_object[:filename]
2923
3262
  else
2924
3263
  link_block_data[LinkKeys::FILE] || @delegate_object[:filename]
@@ -2950,7 +3289,11 @@ module MarkdownExec
2950
3289
  obj[key] = cleaned_value if value.is_a?(Hash) || value.is_a?(Struct)
2951
3290
  end
2952
3291
 
2953
- obj.reject! { |_key, value| [nil, '', [], {}, nil].include?(value) } if obj.is_a?(Hash)
3292
+ if obj.is_a?(Hash)
3293
+ obj.reject! do |_key, value|
3294
+ [nil, '', [], {}, nil].include?(value)
3295
+ end
3296
+ end
2954
3297
 
2955
3298
  obj
2956
3299
  end
@@ -3014,7 +3357,8 @@ module MarkdownExec
3014
3357
 
3015
3358
  # Test case for empty body
3016
3359
  def test_next_link_state
3017
- @hd.next_link_state(block_name_from_cli: nil, was_using_cli: nil, block_state: nil, block_name: nil)
3360
+ @hd.next_link_state(block_name_from_cli: nil, was_using_cli: nil, block_state: nil,
3361
+ block_name: nil)
3018
3362
  end
3019
3363
  end
3020
3364
 
@@ -3061,15 +3405,18 @@ module MarkdownExec
3061
3405
  # Test case for non-empty body with 'file' key
3062
3406
  def test_push_link_history_and_trigger_load_with_file_key
3063
3407
  body = ["file: sample_file\nblock: sample_block\nvars:\n KEY: VALUE"]
3064
- expected_result = LoadFileLinkState.new(LoadFile::LOAD,
3065
- LinkState.new(block_name: 'sample_block',
3066
- document_filename: 'sample_file',
3067
- inherited_dependencies: {},
3068
- inherited_lines: ['# ', 'KEY="VALUE"']))
3408
+ expected_result = LoadFileLinkState.new(
3409
+ LoadFile::LOAD,
3410
+ LinkState.new(block_name: 'sample_block',
3411
+ document_filename: 'sample_file',
3412
+ inherited_dependencies: {},
3413
+ inherited_lines: ['# ', 'KEY="VALUE"'])
3414
+ )
3069
3415
  assert_equal expected_result,
3070
3416
  @hd.push_link_history_and_trigger_load(
3071
3417
  link_block_body: body,
3072
- selected: FCB.new(block_name: 'sample_block', filename: 'sample_file')
3418
+ selected: FCB.new(block_name: 'sample_block',
3419
+ filename: 'sample_file')
3073
3420
  )
3074
3421
  end
3075
3422
 
@@ -3178,20 +3525,21 @@ module MarkdownExec
3178
3525
  end
3179
3526
 
3180
3527
  def test_block_find_with_match
3181
- blocks = [{ key: 'value1' }, { key: 'value2' }]
3182
- result = HashDelegator.block_find(blocks, :key, 'value1')
3183
- assert_equal({ key: 'value1' }, result)
3528
+ blocks = [FCB.new(text: 'value1'), FCB.new(text: 'value2')]
3529
+ result = HashDelegator.block_find(blocks, :text, 'value1')
3530
+ assert_equal('value1', result.text)
3184
3531
  end
3185
3532
 
3186
3533
  def test_block_find_without_match
3187
- blocks = [{ key: 'value1' }, { key: 'value2' }]
3188
- result = HashDelegator.block_find(blocks, :key, 'value3')
3534
+ blocks = [FCB.new(text: 'value1'), FCB.new(text: 'value2')]
3535
+ result = HashDelegator.block_find(blocks, :text, 'missing_value')
3189
3536
  assert_nil result
3190
3537
  end
3191
3538
 
3192
3539
  def test_block_find_with_default
3193
- blocks = [{ key: 'value1' }, { key: 'value2' }]
3194
- result = HashDelegator.block_find(blocks, :key, 'value3', 'default')
3540
+ blocks = [FCB.new(text: 'value1'), FCB.new(text: 'value2')]
3541
+ result = HashDelegator.block_find(blocks, :text, 'missing_value',
3542
+ 'default')
3195
3543
  assert_equal 'default', result
3196
3544
  end
3197
3545
  end
@@ -3227,7 +3575,7 @@ module MarkdownExec
3227
3575
  @hd = HashDelegator.new
3228
3576
  @hd.instance_variable_set(:@delegate_object, {})
3229
3577
  @mdoc = mock('YourMDocClass')
3230
- @selected = { shell: BlockType::VARS, body: ['key: value'] }
3578
+ @selected = FCB.new(shell: BlockType::VARS, body: ['key: value'])
3231
3579
  HashDelegator.stubs(:read_required_blocks_from_temp_file).returns([])
3232
3580
  @hd.stubs(:string_send_color)
3233
3581
  @hd.stubs(:print)
@@ -3236,7 +3584,8 @@ module MarkdownExec
3236
3584
  def test_collect_required_code_lines_with_vars
3237
3585
  YAML.stubs(:load).returns({ 'key' => 'value' })
3238
3586
  @mdoc.stubs(:collect_recursively_required_code).returns({ code: ['code line'] })
3239
- result = @hd.collect_required_code_lines(mdoc: @mdoc, selected: @selected, block_source: {})
3587
+ result = @hd.collect_required_code_lines(mdoc: @mdoc, selected: @selected,
3588
+ block_source: {})
3240
3589
 
3241
3590
  assert_equal ['code line', 'key="value"'], result
3242
3591
  end
@@ -3257,18 +3606,24 @@ module MarkdownExec
3257
3606
 
3258
3607
  result = @hd.load_cli_or_user_selected_block(all_blocks: all_blocks)
3259
3608
 
3260
- assert_equal all_blocks.first.merge(block_name_from_ui: false), result.block
3609
+ assert_equal all_blocks.first,
3610
+ result.block
3611
+ assert_equal OpenStruct.new(block_name_from_ui: false),
3612
+ result.source
3261
3613
  assert_nil result.state
3262
3614
  end
3263
3615
 
3264
3616
  def test_user_selected_block
3265
- block_state = SelectedBlockMenuState.new({ oname: 'block2' },
3617
+ block_state = SelectedBlockMenuState.new({ oname: 'block2' }, OpenStruct.new,
3266
3618
  :some_state)
3267
3619
  @hd.stubs(:wait_for_user_selected_block).returns(block_state)
3268
3620
 
3269
3621
  result = @hd.load_cli_or_user_selected_block
3270
3622
 
3271
- assert_equal block_state.block.merge(block_name_from_ui: true), result.block
3623
+ assert_equal block_state.block,
3624
+ result.block
3625
+ assert_equal OpenStruct.new(block_name_from_ui: true),
3626
+ result.source
3272
3627
  assert_equal :some_state, result.state
3273
3628
  end
3274
3629
  end
@@ -3351,7 +3706,7 @@ module MarkdownExec
3351
3706
  end
3352
3707
 
3353
3708
  def test_determine_block_state_exit
3354
- selected_option = { oname: 'Formatted Option' }
3709
+ selected_option = FCB.new(oname: 'Formatted Option')
3355
3710
  @hd.stubs(:menu_chrome_formatted_option).with(:menu_option_exit_name).returns('Formatted Option')
3356
3711
 
3357
3712
  result = @hd.determine_block_state(selected_option)
@@ -3361,7 +3716,7 @@ module MarkdownExec
3361
3716
  end
3362
3717
 
3363
3718
  def test_determine_block_state_back
3364
- selected_option = { oname: 'Formatted Back Option' }
3719
+ selected_option = FCB.new(oname: 'Formatted Back Option')
3365
3720
  @hd.stubs(:menu_chrome_formatted_option).with(:menu_option_back_name).returns('Formatted Back Option')
3366
3721
  result = @hd.determine_block_state(selected_option)
3367
3722
 
@@ -3370,7 +3725,7 @@ module MarkdownExec
3370
3725
  end
3371
3726
 
3372
3727
  def test_determine_block_state_continue
3373
- selected_option = { oname: 'Other Option' }
3728
+ selected_option = FCB.new(oname: 'Other Option')
3374
3729
 
3375
3730
  result = @hd.determine_block_state(selected_option)
3376
3731
 
@@ -3480,7 +3835,8 @@ module MarkdownExec
3480
3835
  def test_format_execution_stream_with_empty_key
3481
3836
  @hd.instance_variable_get(:@run_state).stubs(:files).returns({})
3482
3837
 
3483
- result = HashDelegator.format_execution_stream(nil, ExecutionStreams::STD_ERR)
3838
+ result = HashDelegator.format_execution_stream(nil,
3839
+ ExecutionStreams::STD_ERR)
3484
3840
 
3485
3841
  assert_equal '', result
3486
3842
  end
@@ -3639,7 +3995,8 @@ module MarkdownExec
3639
3995
  end
3640
3996
 
3641
3997
  def test_iter_blocks_from_nested_files
3642
- @hd.cfile.expect(:readlines, ['line 1', 'line 2'], ['test.md'], import_paths: nil)
3998
+ @hd.cfile.expect(:readlines, ['line 1', 'line 2'], ['test.md'],
3999
+ import_paths: nil)
3643
4000
  selected_messages = ['filtered message']
3644
4001
 
3645
4002
  result = @hd.iter_blocks_from_nested_files { selected_messages }
@@ -3745,7 +4102,8 @@ module MarkdownExec
3745
4102
 
3746
4103
  def test_yield_line_if_selected_with_line
3747
4104
  block_called = false
3748
- HashDelegator.yield_line_if_selected('Test line', [:line]) do |type, content|
4105
+ HashDelegator.yield_line_if_selected('Test line',
4106
+ [:line]) do |type, content|
3749
4107
  block_called = true
3750
4108
  assert_equal :line, type
3751
4109
  assert_equal 'Test line', content.body[0]
@@ -3780,15 +4138,18 @@ module MarkdownExec
3780
4138
  def test_update_menu_attrib_yield_selected_with_body
3781
4139
  HashDelegator.expects(:initialize_fcb_names).with(@fcb)
3782
4140
  HashDelegator.expects(:default_block_title_from_body).with(@fcb)
3783
- Filter.expects(:yield_to_block_if_applicable).with(@fcb, [:some_message], {})
4141
+ Filter.expects(:yield_to_block_if_applicable).with(@fcb, [:some_message],
4142
+ {})
3784
4143
 
3785
- HashDelegator.update_menu_attrib_yield_selected(fcb: @fcb, messages: [:some_message])
4144
+ HashDelegator.update_menu_attrib_yield_selected(fcb: @fcb,
4145
+ messages: [:some_message])
3786
4146
  end
3787
4147
 
3788
4148
  def test_update_menu_attrib_yield_selected_without_body
3789
4149
  @fcb.stubs(:body).returns(nil)
3790
4150
  HashDelegator.expects(:initialize_fcb_names).with(@fcb)
3791
- HashDelegator.update_menu_attrib_yield_selected(fcb: @fcb, messages: [:some_message])
4151
+ HashDelegator.update_menu_attrib_yield_selected(fcb: @fcb,
4152
+ messages: [:some_message])
3792
4153
  end
3793
4154
  end
3794
4155
 
@@ -3864,28 +4225,35 @@ module MarkdownExec
3864
4225
  def test_absolute_path_returns_unchanged
3865
4226
  absolute_path = '/usr/local/bin'
3866
4227
  expression = 'path/to/*/directory'
3867
- assert_equal absolute_path, PathUtils.resolve_path_or_substitute(absolute_path, expression)
4228
+ assert_equal absolute_path,
4229
+ PathUtils.resolve_path_or_substitute(absolute_path,
4230
+ expression)
3868
4231
  end
3869
4232
 
3870
4233
  def test_relative_path_gets_substituted
3871
4234
  relative_path = 'my_folder'
3872
4235
  expression = 'path/to/*/directory'
3873
4236
  expected_output = 'path/to/my_folder/directory'
3874
- assert_equal expected_output, PathUtils.resolve_path_or_substitute(relative_path, expression)
4237
+ assert_equal expected_output,
4238
+ PathUtils.resolve_path_or_substitute(relative_path,
4239
+ expression)
3875
4240
  end
3876
4241
 
3877
4242
  def test_path_with_no_slash_substitutes_correctly
3878
4243
  relative_path = 'data'
3879
4244
  expression = 'path/to/*/directory'
3880
4245
  expected_output = 'path/to/data/directory'
3881
- assert_equal expected_output, PathUtils.resolve_path_or_substitute(relative_path, expression)
4246
+ assert_equal expected_output,
4247
+ PathUtils.resolve_path_or_substitute(relative_path,
4248
+ expression)
3882
4249
  end
3883
4250
 
3884
4251
  def test_empty_path_substitution
3885
4252
  empty_path = ''
3886
4253
  expression = 'path/to/*/directory'
3887
4254
  expected_output = 'path/to//directory'
3888
- assert_equal expected_output, PathUtils.resolve_path_or_substitute(empty_path, expression)
4255
+ assert_equal expected_output,
4256
+ PathUtils.resolve_path_or_substitute(empty_path, expression)
3889
4257
  end
3890
4258
 
3891
4259
  # Test formatting a string containing UTF-8 characters
@@ -3934,7 +4302,8 @@ module MarkdownExec
3934
4302
  private
3935
4303
 
3936
4304
  def prompt_for_filespec_with_wildcard(filespec)
3937
- puts format(@delegate_object[:prompt_show_expr_format], { expr: filespec })
4305
+ puts format(@delegate_object[:prompt_show_expr_format],
4306
+ { expr: filespec })
3938
4307
  puts @delegate_object[:prompt_enter_filespec]
3939
4308
 
3940
4309
  begin