markdown_exec 2.1.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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