markdown_exec 2.7.1 → 2.7.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -91,6 +91,10 @@ module HashDelegatorSelf
91
91
  blocks.find { |item| item.send(msg) == value } || default
92
92
  end
93
93
 
94
+ def block_select(blocks, msg, value, default = nil)
95
+ blocks.select { |item| item.send(msg) == value }
96
+ end
97
+
94
98
  def code_merge(*bodies)
95
99
  merge_lists(*bodies)
96
100
  end
@@ -361,10 +365,10 @@ module HashDelegatorSelf
361
365
  # @param [String] line The line to be processed.
362
366
  # @param [Array<Symbol>] selected_types A list of message types to check.
363
367
  # @param [Proc] block The block to be called with the line data.
364
- def yield_line_if_selected(line, selected_types, id: '', &block)
368
+ def yield_line_if_selected(line, selected_types, source_id: '', &block)
365
369
  return unless block && block_type_selected?(selected_types, :line)
366
370
 
367
- block.call(:line, MarkdownExec::FCB.new(body: [line], id: id))
371
+ block.call(:line, MarkdownExec::FCB.new(body: [line], id: source_id))
368
372
  end
369
373
  end
370
374
 
@@ -600,52 +604,72 @@ module MarkdownExec
600
604
  end
601
605
  end
602
606
 
603
- def add_back_option(id: '', menu_blocks:)
604
- append_chrome_block(id: id, menu_blocks: menu_blocks,
605
- menu_state: MenuState::BACK)
607
+ def add_back_option(menu_blocks:, source_id: '')
608
+ append_chrome_block(
609
+ menu_blocks: menu_blocks,
610
+ menu_state: MenuState::BACK,
611
+ source_id: source_id
612
+ )
606
613
  end
607
614
 
608
- def add_exit_option(id: '', menu_blocks:)
609
- append_chrome_block(id: id, menu_blocks: menu_blocks,
610
- menu_state: MenuState::EXIT)
615
+ def add_exit_option(menu_blocks:, source_id: '')
616
+ append_chrome_block(
617
+ menu_blocks: menu_blocks,
618
+ menu_state: MenuState::EXIT,
619
+ source_id: source_id
620
+ )
611
621
  end
612
622
 
613
- def add_inherited_lines(menu_blocks:, link_state:)
614
- append_inherited_lines(menu_blocks: menu_blocks, link_state: link_state)
623
+ def add_inherited_lines(link_state:, menu_blocks:)
624
+ append_inherited_lines(
625
+ link_state: link_state,
626
+ menu_blocks: menu_blocks
627
+ )
615
628
  end
616
629
 
617
630
  # Modifies the provided menu blocks array by adding 'Back' and 'Exit' options,
618
631
  # along with initial and final dividers, based on the delegate object's configuration.
619
632
  #
620
633
  # @param menu_blocks [Array] The array of menu block elements to be modified.
621
- def add_menu_chrome_blocks!(id: '', menu_blocks:, link_state:)
634
+ def add_menu_chrome_blocks!(link_state:, menu_blocks:, source_id: '')
622
635
  return unless @delegate_object[:menu_link_format].present?
623
636
 
624
- if @delegate_object[:menu_with_inherited_lines]
625
- add_inherited_lines(menu_blocks: menu_blocks,
626
- link_state: link_state)
627
- end
637
+ add_inherited_lines(
638
+ link_state: link_state,
639
+ menu_blocks: menu_blocks
640
+ ) if @delegate_object[:menu_with_inherited_lines]
628
641
 
629
642
  # back before exit
630
- add_back_option(id: "#{id}.back",
631
- menu_blocks: menu_blocks) if should_add_back_option?
643
+ add_back_option(
644
+ menu_blocks: menu_blocks,
645
+ source_id: "#{source_id}.back"
646
+ ) if should_add_back_option?
632
647
 
633
648
  # exit after other options
634
- if @delegate_object[:menu_with_exit]
635
- add_exit_option(id: "#{id}.exit", menu_blocks: menu_blocks)
636
- end
637
649
 
638
- append_divider(id: "#{id}.init", menu_blocks: menu_blocks,
639
- position: :initial)
640
- append_divider(id: "#{id}.final", menu_blocks: menu_blocks,
641
- position: :final)
650
+ add_exit_option(
651
+ menu_blocks: menu_blocks,
652
+ source_id: "#{source_id}.exit"
653
+ ) if @delegate_object[:menu_with_exit]
654
+
655
+ append_divider(
656
+ menu_blocks: menu_blocks,
657
+ position: :initial,
658
+ source_id: "#{source_id}.init"
659
+ )
660
+
661
+ append_divider(
662
+ menu_blocks: menu_blocks,
663
+ position: :final,
664
+ source_id: "#{source_id}.final"
665
+ )
642
666
  end
643
667
 
644
668
  # Appends a chrome block, which is a menu option for Back or Exit
645
669
  #
646
670
  # @param all_blocks [Array] The current blocks in the menu
647
671
  # @param type [Symbol] The type of chrome block to add (:back or :exit)
648
- def append_chrome_block(menu_blocks:, menu_state:, id: '')
672
+ def append_chrome_block(menu_blocks:, menu_state:, source_id: '')
649
673
  case menu_state
650
674
  when MenuState::BACK
651
675
  history_state_partition
@@ -683,7 +707,7 @@ module MarkdownExec
683
707
  dname: HashDelegator.new(@delegate_object).string_send_color(
684
708
  formatted_name, :menu_chrome_color
685
709
  ),
686
- id: id,
710
+ id: source_id.to_s,
687
711
  type: BlockType::CHROME,
688
712
  nickname: formatted_name,
689
713
  oname: formatted_name
@@ -703,10 +727,10 @@ module MarkdownExec
703
727
  #
704
728
  # @param menu_blocks [Array] The array of menu block elements.
705
729
  # @param position [Symbol] The position to insert the divider (:initial or :final).
706
- def append_divider(id: '', menu_blocks:, position:)
730
+ def append_divider(menu_blocks:, position:, source_id: '')
707
731
  return unless divider_formatting_present?(position)
708
732
 
709
- divider = create_divider(position, id: id)
733
+ divider = create_divider(position, source_id: source_id)
710
734
  position == :initial ? menu_blocks.unshift(divider) : menu_blocks.push(divider)
711
735
  end
712
736
 
@@ -715,7 +739,7 @@ module MarkdownExec
715
739
  #
716
740
  # @param menu_blocks [Array] The array of menu block elements.
717
741
  # @param position [Symbol] The position to insert the divider (:initial or :final).
718
- def append_inherited_lines(menu_blocks:, link_state:, position: top)
742
+ def append_inherited_lines(link_state:, menu_blocks:, position: top)
719
743
  return unless link_state.inherited_lines_present?
720
744
 
721
745
  insert_at_top = @delegate_object[:menu_inherited_lines_at_top]
@@ -783,13 +807,31 @@ module MarkdownExec
783
807
 
784
808
  # private
785
809
 
810
+ def expand_references!(fcb, link_state)
811
+ expand_variable_references!(
812
+ blocks: [fcb],
813
+ initial_code_required: false,
814
+ link_state: link_state
815
+ )
816
+ expand_variable_references!(
817
+ blocks: [fcb],
818
+ echo_format: '%s',
819
+ group_name: :command,
820
+ initial_code_required: false,
821
+ key_format: '$(%s)',
822
+ link_state: link_state,
823
+ pattern: options_command_substitution_regexp
824
+ )
825
+ end
826
+
786
827
  # Iterates through nested files to collect various types
787
828
  # of blocks, including dividers, tasks, and others.
788
829
  # The method categorizes blocks based on their type and processes them accordingly.
789
830
  #
790
831
  # @return [Array<FCB>] An array of FCB objects representing the blocks.
791
832
  def blocks_from_nested_files(
792
- link_state: @dml_link_state || LinkState.new
833
+ link_state: @dml_link_state || LinkState.new,
834
+ source_id: nil
793
835
  )
794
836
  register_console_attributes(@delegate_object)
795
837
  @decor_patterns_from_delegate_object_for_block_create = collect_line_decor_patterns(@delegate_object)
@@ -799,22 +841,6 @@ module MarkdownExec
799
841
  iter_blocks_from_nested_files do |btype, fcb|
800
842
  count += 1
801
843
 
802
- # text substitution in menu
803
- #
804
- expand_references = lambda do |fcb|
805
- expand_variable_references!(blocks: [fcb], link_state: link_state,
806
- initial_code_required: false)
807
- expand_variable_references!(
808
- blocks: [fcb],
809
- echo_format: '%s',
810
- initial_code_required: false,
811
- key_format: '$(%s)',
812
- link_state: link_state,
813
- group_name: :command,
814
- pattern: options_command_substitution_regexp
815
- )
816
- end
817
-
818
844
  case btype
819
845
  when :blocks
820
846
  if @delegate_object[:bash]
@@ -822,20 +848,25 @@ module MarkdownExec
822
848
  block_calls_scan: @delegate_object[:block_calls_scan],
823
849
  block_name_match: @delegate_object[:block_name_match],
824
850
  block_name_nick_match: @delegate_object[:block_name_nick_match],
825
- id: "*#{count}"
851
+ id: "#{source_id}_bfnf_b_#{count}"
826
852
  ) do |oname, color|
827
853
  apply_block_type_color_option(oname, color)
828
854
  end
855
+ else
856
+ expand_references!(fcb, link_state)
829
857
  end
830
- expand_references.call(fcb)
831
858
  blocks << fcb
832
859
  when :filter # types accepted
833
860
  %i[blocks line]
834
861
  when :line
835
862
  unless @delegate_object[:no_chrome]
836
- expand_references.call(fcb)
837
- create_and_add_chrome_blocks(blocks, fcb, id: "*#{count}",
838
- init_ids: init_ids)
863
+ # expand references only if block is recognized (not a comment)
864
+ create_and_add_chrome_blocks(
865
+ blocks, fcb, id: "#{source_id}_bfnf_l_#{count}", init_ids: init_ids
866
+ ) do
867
+ # expand references only if block is recognized (not a comment)
868
+ expand_references!(fcb, link_state)
869
+ end
839
870
  end
840
871
  end
841
872
  end
@@ -952,16 +983,19 @@ module MarkdownExec
952
983
 
953
984
  # sets ENV
954
985
  def code_from_vars_block_to_set_environment_variables(selected)
955
- code_lines = []
956
- YAML.load(selected.body.join("\n"))&.each do |key, value|
957
- ENV[key] = value.to_s
958
- code_lines.push "#{key}=#{Shellwords.escape(value)}"
986
+ code_lines = []
987
+ case data = YAML.load(selected.body.join("\n"))
988
+ when Hash
989
+ data.each do |key, value|
990
+ ENV[key] = value.to_s
991
+ code_lines.push "#{key}=#{Shellwords.escape(value)}"
959
992
 
960
- next unless @delegate_object[:menu_vars_set_format].present?
993
+ next unless @delegate_object[:menu_vars_set_format].present?
961
994
 
962
- formatted_string = format(@delegate_object[:menu_vars_set_format],
963
- { key: key, value: value })
964
- print string_send_color(formatted_string, :menu_vars_set_color)
995
+ formatted_string = format(@delegate_object[:menu_vars_set_format],
996
+ { key: key, value: value })
997
+ print string_send_color(formatted_string, :menu_vars_set_color)
998
+ end
965
999
  end
966
1000
  code_lines
967
1001
  end
@@ -1134,8 +1168,10 @@ module MarkdownExec
1134
1168
  end
1135
1169
 
1136
1170
  # Check if the expression contains wildcard characters
1137
- def contains_wildcards?(expr)
1138
- expr.match(%r{\*|\?|\[})
1171
+ def contains_glob?(str)
1172
+ return false if str.nil?
1173
+
1174
+ str.match?(/[\*\?\[\{\}]/)
1139
1175
  end
1140
1176
 
1141
1177
  def copy_to_clipboard(required_lines)
@@ -1200,13 +1236,15 @@ module MarkdownExec
1200
1236
  collapse: nil, color_method:, decor_patterns: [],
1201
1237
  disabled: true, format_option:, id: '',
1202
1238
  level: 0, match_data:, type: '',
1203
- wrap: nil
1239
+ wrap: nil,
1240
+ fcb: nil
1204
1241
  )
1205
- line_cap = NamedCaptureExtractor.extract_named_group2(match_data)
1242
+ ww 'match_data', match_data
1243
+ line_cap = NamedCaptureExtractor.extract_named_group_match_data(match_data)
1206
1244
  # replace tabs in indent
1207
1245
  line_cap[:indent] ||= ''
1208
1246
  line_cap[:indent] = line_cap[:indent].dup if line_cap[:indent].frozen?
1209
- line_cap[:indent].gsub!("\t", ' ')
1247
+ line_cap[:indent].gsub!("\t", ' ') # TAB_SIZE = 4
1210
1248
  # replace tabs in text
1211
1249
  line_cap[:text] ||= ''
1212
1250
  line_cap[:text] = line_cap[:text].dup if line_cap[:text].frozen?
@@ -1234,6 +1272,7 @@ module MarkdownExec
1234
1272
  end
1235
1273
  end
1236
1274
 
1275
+ use_fcb = !fcb.nil? # fcb only for the first record if any
1237
1276
  line_caps.each_with_index do |line_obj, index|
1238
1277
  next if line_obj[:text].nil?
1239
1278
 
@@ -1256,23 +1295,45 @@ module MarkdownExec
1256
1295
  )
1257
1296
 
1258
1297
  line_obj[:line] = line_obj[:indent] + line_obj[:text]
1259
- blocks.push FCB.new(
1260
- center: center,
1261
- chrome: true,
1262
- collapse: collapse.nil? ? (line_obj[:collapse] == COLLAPSIBLE_TOKEN_COLLAPSE) : collapse,
1263
- token: line_obj[:collapse],
1264
- disabled: disabled ? TtyMenu::DISABLE : nil,
1265
- id: "#{id}.#{index}",
1266
- level: level,
1267
- s0indent: indent,
1268
- s0printable: line_obj[:text],
1269
- s1decorated: decorated,
1270
- dname: line_obj[:indent] + decorated,
1271
- indent: line_obj[:indent],
1272
- oname: line_obj[:text],
1273
- text: line_obj[:text],
1274
- type: type
1275
- )
1298
+
1299
+ if !use_fcb
1300
+ fcb = FCB.new(
1301
+ center: center,
1302
+ chrome: true,
1303
+ collapse: collapse.nil? ? (line_obj[:collapse] == COLLAPSIBLE_TOKEN_COLLAPSE) : collapse,
1304
+ token: line_obj[:collapse],
1305
+ disabled: disabled ? TtyMenu::DISABLE : nil,
1306
+ id: "#{id}.#{index}",
1307
+ level: level,
1308
+ s0indent: indent,
1309
+ s0printable: line_obj[:text],
1310
+ s1decorated: decorated,
1311
+ dname: line_obj[:indent] + decorated,
1312
+ indent: line_obj[:indent],
1313
+ oname: line_obj[:text],
1314
+ text: line_obj[:text],
1315
+ type: type
1316
+ )
1317
+ else
1318
+ fcb.center = center
1319
+ fcb.chrome = true
1320
+ fcb.collapse = collapse.nil? ? (line_obj[:collapse] == COLLAPSIBLE_TOKEN_COLLAPSE) : collapse
1321
+ fcb.token = line_obj[:collapse]
1322
+ fcb.disabled = disabled ? TtyMenu::DISABLE : nil
1323
+ fcb.id = "#{id}.#{index}"
1324
+ fcb.level = level
1325
+ fcb.s0indent = indent
1326
+ fcb.s0printable = line_obj[:text]
1327
+ fcb.s1decorated = decorated
1328
+ fcb.dname = line_obj[:indent] + decorated
1329
+ fcb.indent = line_obj[:indent]
1330
+ fcb.oname = line_obj[:text]
1331
+ fcb.text = line_obj[:text]
1332
+ fcb.type = type
1333
+ use_fcb = false # next line is new record
1334
+ end
1335
+
1336
+ blocks.push fcb
1276
1337
  end
1277
1338
  line_caps.count
1278
1339
  end
@@ -1292,6 +1353,12 @@ module MarkdownExec
1292
1353
  next
1293
1354
  end
1294
1355
 
1356
+ if block_given?
1357
+ # expand references only if block is recognized (not a comment)
1358
+ yield if block_given?
1359
+ mbody = fcb.body[0].match @delegate_object[criteria[:match]]
1360
+ end
1361
+
1295
1362
  create_and_add_chrome_block(
1296
1363
  blocks: blocks,
1297
1364
  case_conversion: criteria[:case_conversion],
@@ -1312,6 +1379,7 @@ module MarkdownExec
1312
1379
  decor_patterns:
1313
1380
  @decor_patterns_from_delegate_object_for_block_create,
1314
1381
  disabled: !(criteria[:collapsible] && @delegate_object[criteria[:collapsible]]),
1382
+ fcb: fcb,
1315
1383
  id: "#{id}.#{index}",
1316
1384
  format_option: criteria[:format] &&
1317
1385
  @delegate_object[criteria[:format]],
@@ -1324,7 +1392,7 @@ module MarkdownExec
1324
1392
  end
1325
1393
  end
1326
1394
 
1327
- def create_divider(position, id: '')
1395
+ def create_divider(position, source_id: '')
1328
1396
  divider_key = if position == :initial
1329
1397
  :menu_initial_divider
1330
1398
  else
@@ -1337,7 +1405,7 @@ module MarkdownExec
1337
1405
  chrome: true,
1338
1406
  disabled: TtyMenu::DISABLE,
1339
1407
  dname: string_send_color(oname, :menu_divider_color),
1340
- id: id,
1408
+ id: source_id,
1341
1409
  oname: oname
1342
1410
  )
1343
1411
  end
@@ -1420,9 +1488,10 @@ module MarkdownExec
1420
1488
 
1421
1489
  def dml_menu_append_chrome_item(
1422
1490
  name, count, type,
1423
- id: '',
1491
+ always_create: true,
1492
+ always_enable: true,
1424
1493
  menu_state: MenuState::LOAD,
1425
- always_create: true, always_enable: true
1494
+ source_id: ''
1426
1495
  )
1427
1496
  raise unless name.present?
1428
1497
  raise if @dml_menu_blocks.nil?
@@ -1432,7 +1501,7 @@ module MarkdownExec
1432
1501
  # create menu item when it is needed (count > 0)
1433
1502
  #
1434
1503
  if item.nil? && (always_create || count.positive?)
1435
- item = append_chrome_block(id: id,
1504
+ item = append_chrome_block(source_id: source_id,
1436
1505
  menu_blocks: @dml_menu_blocks,
1437
1506
  menu_state: menu_state)
1438
1507
  end
@@ -1688,11 +1757,7 @@ module MarkdownExec
1688
1757
 
1689
1758
  elsif COLLAPSIBLE_TYPES.include?(selected.type)
1690
1759
  debounce_reset
1691
- if @compressed_ids.keys.include?(selected.id)
1692
- @compressed_ids.delete(selected.id)
1693
- else
1694
- @compressed_ids.merge!(selected.id => selected.level)
1695
- end
1760
+ menu_toggle_collapsible_block(selected)
1696
1761
  LoadFileLinkState.new(LoadFile::REUSE, link_state)
1697
1762
 
1698
1763
  elsif debounce_allows
@@ -1880,18 +1945,28 @@ module MarkdownExec
1880
1945
  view: @delegate_object[:vars_block_filename_view]
1881
1946
  )
1882
1947
  block_data = HashDelegator.parse_yaml_data_from_body(selected.body)
1883
- if selected_option = select_option_with_metadata(
1948
+
1949
+ dirs = Dir.glob(
1950
+ File.join(
1951
+ Dir.pwd,
1952
+ block_data['directory'] || directory,
1953
+ block_data['glob'] || glob
1954
+ )
1955
+ )
1956
+
1957
+ if !contains_glob?(block_data['directory']) &&
1958
+ !contains_glob?(block_data['glob'])
1959
+ if dirs[0]
1960
+ File.readlines(dirs[0], chomp: true)
1961
+ else
1962
+ warn 'No matching file found.'
1963
+ end
1964
+ elsif selected_option = select_option_with_metadata(
1884
1965
  prompt_title,
1885
- [exit_prompt] + Dir.glob(
1886
- File.join(
1887
- Dir.pwd,
1888
- block_data['directory'] || directory,
1889
- block_data['glob'] || glob
1890
- )
1891
- ).sort.map do |file|
1966
+ [exit_prompt] + dirs.sort.map do |file|
1892
1967
  { name: format(
1893
1968
  block_data['view'] || view,
1894
- NamedCaptureExtractor.extract_named_group2(
1969
+ NamedCaptureExtractor.extract_named_group_match_data(
1895
1970
  file.match(
1896
1971
  Regexp.new(block_data['filename_pattern'] ||
1897
1972
  filename_pattern)
@@ -1908,7 +1983,7 @@ module MarkdownExec
1908
1983
  File.readlines(selected_option.oname, chomp: true)
1909
1984
  end
1910
1985
  else
1911
- warn 'No matching files found'
1986
+ warn 'No matching files found.'
1912
1987
  end
1913
1988
  end
1914
1989
 
@@ -2397,7 +2472,7 @@ module MarkdownExec
2397
2472
  Regexp.new(@delegate_object.fetch(
2398
2473
  :fenced_start_and_end_regex, '^(?<indent> *)`{3,}'
2399
2474
  )),
2400
- fcb: MarkdownExec::FCB.new,
2475
+ fcb: MarkdownExec::FCB.new(id: 'INIT'),
2401
2476
  in_fenced_block: false,
2402
2477
  headings: []
2403
2478
  }
@@ -2419,16 +2494,16 @@ module MarkdownExec
2419
2494
 
2420
2495
  update_line_and_block_state(
2421
2496
  nested_line, state, selected_types,
2422
- id: "#{@delegate_object[:filename]}:#{index}",
2497
+ source_id: "#{@delegate_object[:filename]}_ibfnf_#{index}",
2423
2498
  &block
2424
2499
  )
2425
2500
  end
2426
2501
  end
2427
2502
 
2428
- def iter_source_blocks(source, &block)
2503
+ def iter_source_blocks(source, source_id: nil, &block)
2429
2504
  case source
2430
2505
  when 1
2431
- blocks_from_nested_files.each(&block)
2506
+ blocks_from_nested_files(source_id: source_id).each(&block)
2432
2507
  when 2
2433
2508
  @dml_blocks_in_file.each(&block)
2434
2509
  when 3
@@ -2487,20 +2562,29 @@ module MarkdownExec
2487
2562
  label_format_above = @delegate_object[:shell_code_label_format_above]
2488
2563
  label_format_below = @delegate_object[:shell_code_label_format_below]
2489
2564
 
2490
- [label_format_above.present? &&
2491
- format(label_format_above,
2492
- block_source.merge({ block_name: selected.pub_name }))] +
2493
- output_lines.map do |line|
2494
- re = Regexp.new(link_block_data.fetch('pattern', '(?<line>.*)'))
2495
- next unless re =~ line
2496
-
2497
- re.gsub_format(line,
2498
- link_block_data.fetch('format',
2499
- '%{line}'))
2500
- end.compact +
2501
- [label_format_below.present? &&
2502
- format(label_format_below,
2503
- block_source.merge({ block_name: selected.pub_name }))]
2565
+ ([
2566
+ label_format_above.present? ?
2567
+ format(
2568
+ label_format_above,
2569
+ block_source.merge({ block_name: selected.pub_name })
2570
+ ) : nil
2571
+ ] +
2572
+ output_lines.map do |line|
2573
+ re = Regexp.new(link_block_data.fetch('pattern', '(?<line>.*)'))
2574
+ next unless re =~ line
2575
+
2576
+ re.gsub_format(
2577
+ line,
2578
+ link_block_data.fetch('format', '%{line}')
2579
+ )
2580
+ end +
2581
+ [
2582
+ label_format_below.present? ?
2583
+ format(
2584
+ label_format_below,
2585
+ block_source.merge({ block_name: selected.pub_name })
2586
+ ) : nil
2587
+ ]).compact
2504
2588
  end
2505
2589
 
2506
2590
  def link_history_push_and_next(
@@ -2548,12 +2632,14 @@ module MarkdownExec
2548
2632
  }
2549
2633
  end
2550
2634
 
2551
- def list_blocks
2635
+ def list_blocks(source_id: nil)
2552
2636
  message = @delegate_object[:list_blocks_message]
2553
2637
  block_eval = @delegate_object[:list_blocks_eval]
2554
2638
 
2555
2639
  list = []
2556
- iter_source_blocks(@delegate_object[:list_blocks_type]) do |block|
2640
+ iter_source_blocks(
2641
+ @delegate_object[:list_blocks_type], source_id: source_id
2642
+ ) do |block|
2557
2643
  list << (block_eval.present? ? eval(block_eval) : block.send(message))
2558
2644
  end
2559
2645
  list.compact!
@@ -2566,21 +2652,25 @@ module MarkdownExec
2566
2652
  # Executes a specified block once per filename.
2567
2653
  # @param all_blocks [Array] Array of all block elements.
2568
2654
  # @return [Boolean, nil] True if values were modified, nil otherwise.
2569
- def load_auto_opts_block(all_blocks, id: '', mdoc:)
2655
+ def load_auto_opts_block(all_blocks, mdoc:)
2570
2656
  block_name = @delegate_object[:document_load_opts_block_name]
2571
2657
  unless block_name.present? &&
2572
2658
  @opts_most_recent_filename != @delegate_object[:filename]
2573
2659
  return
2574
2660
  end
2575
2661
 
2576
- block = HashDelegator.block_find(all_blocks, :oname, block_name)
2577
- return unless block
2662
+ blocks = HashDelegator.block_select(all_blocks, :oname, block_name)
2663
+ return if blocks.empty?
2578
2664
 
2579
- options_state = read_show_options_and_trigger_reuse(
2580
- mdoc: mdoc,
2581
- selected: block
2665
+ update_menu_base(
2666
+ blocks.each.with_object({}) do |block, merged_options|
2667
+ options_state = read_show_options_and_trigger_reuse(
2668
+ mdoc: mdoc,
2669
+ selected: block
2670
+ )
2671
+ merged_options.merge!(options_state.options)
2672
+ end
2582
2673
  )
2583
- update_menu_base(options_state.options)
2584
2674
 
2585
2675
  @opts_most_recent_filename = @delegate_object[:filename]
2586
2676
  true
@@ -2593,11 +2683,16 @@ module MarkdownExec
2593
2683
  return
2594
2684
  end
2595
2685
 
2596
- block = HashDelegator.block_find(all_blocks, :oname, block_name)
2597
- return unless block
2686
+ blocks = HashDelegator.block_select(all_blocks, :oname, block_name)
2687
+ return if blocks.empty?
2598
2688
 
2599
2689
  @vars_most_recent_filename = @delegate_object[:filename]
2600
- code_from_vars_block_to_set_environment_variables(block)
2690
+
2691
+ (blocks.each.with_object([]) do |block, merged_options|
2692
+ merged_options.push(
2693
+ code_from_vars_block_to_set_environment_variables(block)
2694
+ )
2695
+ end).to_a
2601
2696
  end
2602
2697
 
2603
2698
  def load_cli_or_user_selected_block(all_blocks: [], menu_blocks: [],
@@ -2620,6 +2715,30 @@ module MarkdownExec
2620
2715
  SelectedBlockMenuState.new(block, source, state)
2621
2716
  end
2622
2717
 
2718
+ def load_document_shell_block(all_blocks, mdoc: nil)
2719
+ block_name = @delegate_object[:document_load_shell_block_name]
2720
+ unless block_name.present? &&
2721
+ @shell_most_recent_filename != @delegate_object[:filename]
2722
+ return
2723
+ end
2724
+
2725
+ fcb = HashDelegator.block_find(all_blocks, :oname, block_name)
2726
+ return unless fcb
2727
+
2728
+ @shell_most_recent_filename = @delegate_object[:filename]
2729
+
2730
+ if mdoc
2731
+ mdoc.collect_recursively_required_code(
2732
+ anyname: fcb.pub_name,
2733
+ label_format_above: @delegate_object[:shell_code_label_format_above],
2734
+ label_format_below: @delegate_object[:shell_code_label_format_below],
2735
+ block_source: block_source
2736
+ )[:code]
2737
+ else
2738
+ fcb.body
2739
+ end
2740
+ end
2741
+
2623
2742
  # format + glob + select for file in load block
2624
2743
  # name has references to ENV vars and doc and batch vars
2625
2744
  # incl. timestamp
@@ -2628,7 +2747,7 @@ module MarkdownExec
2628
2747
  expanded_expression = formatted_expression(expression)
2629
2748
 
2630
2749
  # Handle wildcards or direct file specification
2631
- if contains_wildcards?(expanded_expression)
2750
+ if contains_glob?(expanded_expression)
2632
2751
  load_filespec_wildcard_expansion(expanded_expression)
2633
2752
  else
2634
2753
  expanded_expression
@@ -2676,8 +2795,8 @@ module MarkdownExec
2676
2795
  [block_name_from_cli, now_using_cli]
2677
2796
  end
2678
2797
 
2679
- def mdoc_and_blocks_from_nested_files
2680
- menu_blocks = blocks_from_nested_files
2798
+ def mdoc_and_blocks_from_nested_files(source_id: nil)
2799
+ menu_blocks = blocks_from_nested_files(source_id: source_id)
2681
2800
  mdoc = MDoc.new(menu_blocks) do |nopts|
2682
2801
  @delegate_object.merge!(nopts)
2683
2802
  end
@@ -2687,12 +2806,19 @@ module MarkdownExec
2687
2806
  ## Handles the file loading and returns the blocks
2688
2807
  # in the file and MDoc instance
2689
2808
  #
2690
- def mdoc_menu_and_blocks_from_nested_files(link_state, id: '')
2809
+ def mdoc_menu_and_blocks_from_nested_files(link_state, source_id: '')
2691
2810
  # read blocks, load document opts block, and re-process blocks
2692
2811
  #
2693
- all_blocks, mdoc = mdoc_and_blocks_from_nested_files
2694
- if load_auto_opts_block(all_blocks, id: id, mdoc: mdoc)
2695
- all_blocks, mdoc = mdoc_and_blocks_from_nested_files
2812
+ all_blocks, mdoc = mdoc_and_blocks_from_nested_files(source_id: source_id)
2813
+ if load_auto_opts_block(all_blocks, mdoc: mdoc)
2814
+ all_blocks, mdoc = mdoc_and_blocks_from_nested_files(source_id: source_id)
2815
+ end
2816
+
2817
+ # load document shell block
2818
+ #
2819
+ if code_lines = load_document_shell_block(all_blocks, mdoc: mdoc)
2820
+ next_state_set_code(nil, link_state, code_lines)
2821
+ link_state.inherited_lines = code_lines
2696
2822
  end
2697
2823
 
2698
2824
  # load document vars block
@@ -2700,11 +2826,7 @@ module MarkdownExec
2700
2826
  if code_lines = load_auto_vars_block(all_blocks)
2701
2827
  new_code = HashDelegator.code_merge(link_state.inherited_lines,
2702
2828
  code_lines)
2703
- next_state_set_code(
2704
- nil,
2705
- link_state,
2706
- new_code
2707
- )
2829
+ next_state_set_code(nil, link_state, new_code)
2708
2830
  link_state.inherited_lines = new_code
2709
2831
  end
2710
2832
 
@@ -2714,10 +2836,22 @@ module MarkdownExec
2714
2836
  @delegate_object.merge!(compressed_ids: @compressed_ids)
2715
2837
  )
2716
2838
 
2839
+ # re-expand blocks
2840
+ menu_blocks.each do |fcb|
2841
+ fcb.body = fcb.raw_body || fcb.body || []
2842
+ fcb.dname = fcb.raw_dname || fcb.dname
2843
+ fcb.s0printable = fcb.raw_s0printable || fcb.s0printable
2844
+ fcb.s1decorated = fcb.raw_s1decorated || fcb.s1decorated
2845
+ expand_references!(fcb, link_state)
2846
+ end
2847
+
2717
2848
  # chrome for menu
2718
2849
  #
2719
- add_menu_chrome_blocks!(id: id, menu_blocks: menu_blocks,
2720
- link_state: link_state)
2850
+ add_menu_chrome_blocks!(
2851
+ link_state: link_state,
2852
+ menu_blocks: menu_blocks,
2853
+ source_id: source_id
2854
+ )
2721
2855
 
2722
2856
  ### compress empty lines
2723
2857
  HashDelegator.delete_consecutive_blank_lines!(menu_blocks)
@@ -2743,6 +2877,7 @@ module MarkdownExec
2743
2877
  dname: HashDelegator.new(@delegate_object).string_send_color(
2744
2878
  document_glob, :menu_inherited_lines_color
2745
2879
  ),
2880
+ # 2025-01-03 menu item is disabled ∴ does not need a recall id
2746
2881
  oname: formatted_name
2747
2882
  )
2748
2883
 
@@ -2783,6 +2918,22 @@ module MarkdownExec
2783
2918
  end
2784
2919
  end
2785
2920
 
2921
+ def menu_compress_collapsible_block(selected)
2922
+ @compressed_ids.merge!(selected.id => selected.level)
2923
+ end
2924
+
2925
+ def menu_expand_collapsible_block(selected)
2926
+ @compressed_ids.delete(selected.id)
2927
+ end
2928
+
2929
+ def menu_toggle_collapsible_block(selected)
2930
+ if @compressed_ids.keys.include?(selected.id)
2931
+ menu_expand_collapsible_block(selected)
2932
+ else
2933
+ menu_compress_collapsible_block(selected)
2934
+ end
2935
+ end
2936
+
2786
2937
  # If a method is missing, treat it as a key for the @delegate_object.
2787
2938
  def method_missing(method_name, *args, &block)
2788
2939
  if @delegate_object.respond_to?(method_name)
@@ -3435,7 +3586,7 @@ module MarkdownExec
3435
3586
  formatted = formatted_expression(expression)
3436
3587
 
3437
3588
  # Handle wildcards or direct file specification
3438
- if contains_wildcards?(formatted)
3589
+ if contains_glob?(formatted)
3439
3590
  save_filespec_wildcard_expansion(formatted)
3440
3591
  else
3441
3592
  formatted
@@ -3579,11 +3730,14 @@ module MarkdownExec
3579
3730
  item == selection
3580
3731
  end
3581
3732
  end
3733
+
3734
+ # new FCB if selected is not an object
3582
3735
  if selected.instance_of?(String)
3583
3736
  selected = FCB.new(dname: selected)
3584
3737
  elsif selected.instance_of?(Hash)
3585
3738
  selected = FCB.new(selected)
3586
3739
  end
3740
+
3587
3741
  unless selected
3588
3742
  HashDelegator.error_handler('select_option_with_metadata',
3589
3743
  error: 'menu item not found')
@@ -3651,7 +3805,9 @@ module MarkdownExec
3651
3805
  # @param fenced_start_extended_regex [Regexp]
3652
3806
  # Regular expression to identify fenced block start.
3653
3807
  # @return [MarkdownExec::FCB] A new FCB instance with the parsed attributes.
3654
- def start_fenced_block(line, headings, fenced_start_extended_regex)
3808
+ def start_fenced_block(
3809
+ line, headings, fenced_start_extended_regex, source_id: nil
3810
+ )
3655
3811
  fcb_title_groups = NamedCaptureExtractor.extract_named_groups(
3656
3812
  line, fenced_start_extended_regex
3657
3813
  )
@@ -3702,6 +3858,7 @@ module MarkdownExec
3702
3858
  disabled: disabled,
3703
3859
  dname: dname,
3704
3860
  headings: headings,
3861
+ id: source_id.to_s,
3705
3862
  indent: fcb_title_groups.fetch(:indent, ''),
3706
3863
  nickname: nickname,
3707
3864
  oname: oname,
@@ -3709,10 +3866,10 @@ module MarkdownExec
3709
3866
  shell: fcb_title_groups.fetch(:shell, ''),
3710
3867
  start_line: line,
3711
3868
  stdin: if (tn = rest.match(/<(?<type>\$)?(?<name>[A-Za-z_-]\S+)/))
3712
- NamedCaptureExtractor.extract_named_group2(tn)
3869
+ NamedCaptureExtractor.extract_named_group_match_data(tn)
3713
3870
  end,
3714
3871
  stdout: if (tn = rest.match(/>(?<type>\$)?(?<name>[\w.\-]+)/))
3715
- NamedCaptureExtractor.extract_named_group2(tn)
3872
+ NamedCaptureExtractor.extract_named_group_match_data(tn)
3716
3873
  end,
3717
3874
  title: title,
3718
3875
  type: fcb_title_groups.fetch(:type, ''),
@@ -3764,7 +3921,7 @@ module MarkdownExec
3764
3921
  ##
3765
3922
  def update_line_and_block_state(
3766
3923
  nested_line, state, selected_types,
3767
- id:,
3924
+ source_id:,
3768
3925
  &block
3769
3926
  )
3770
3927
  line = nested_line.to_s
@@ -3784,7 +3941,8 @@ module MarkdownExec
3784
3941
  #
3785
3942
  state[:fcb] = start_fenced_block(
3786
3943
  line, state[:headings],
3787
- @delegate_object[:fenced_start_extended_regex]
3944
+ @delegate_object[:fenced_start_extended_regex],
3945
+ source_id: source_id
3788
3946
  )
3789
3947
  state[:fcb][:depth] = nested_line[:depth]
3790
3948
  state[:fcb][:indention] = nested_line[:indention]
@@ -3801,8 +3959,9 @@ module MarkdownExec
3801
3959
  @delegate_object[:menu_include_imported_notes]
3802
3960
  # add line if it is depth 0 or option allows it
3803
3961
  #
3804
- HashDelegator.yield_line_if_selected(line, selected_types, id: id,
3805
- &block)
3962
+ HashDelegator.yield_line_if_selected(
3963
+ line, selected_types, source_id: source_id, &block
3964
+ )
3806
3965
  end
3807
3966
  end
3808
3967
 
@@ -4078,15 +4237,20 @@ module MarkdownExec
4078
4237
  end
4079
4238
  end
4080
4239
 
4240
+ count = 0
4081
4241
  InputSequencer.new(
4082
4242
  @delegate_object[:filename],
4083
4243
  block_list
4084
4244
  ).run do |msg, data|
4245
+ count += 1
4085
4246
  case msg
4086
4247
  when :parse_document # once for each menu
4087
- vux_parse_document(id: 'vux_parse_document')
4088
- vux_menu_append_history_files(formatted_choice_ostructs,
4089
- id: 'vux_menu_append_history_files')
4248
+ count = 0
4249
+ vux_parse_document(source_id: "#{@delegate_object[:filename]}_vmlpd")
4250
+ vux_menu_append_history_files(
4251
+ formatted_choice_ostructs,
4252
+ source_id: "#{@delegate_object[:filename]}_vmlhf"
4253
+ )
4090
4254
  vux_publish_document_file_name_for_external_automation
4091
4255
 
4092
4256
  when :display_menu
@@ -4097,7 +4261,7 @@ module MarkdownExec
4097
4261
  # yield :end_of_cli, @delegate_object
4098
4262
 
4099
4263
  if @delegate_object[:list_blocks]
4100
- list_blocks
4264
+ list_blocks(source_id: "#{@delegate_object[:filename]}_vmleoc")
4101
4265
  :exit
4102
4266
  end
4103
4267
 
@@ -4132,8 +4296,9 @@ module MarkdownExec
4132
4296
  end
4133
4297
  end
4134
4298
 
4135
- def vux_menu_append_history_files(formatted_choice_ostructs,
4136
- id: '')
4299
+ def vux_menu_append_history_files(
4300
+ formatted_choice_ostructs, source_id: ''
4301
+ )
4137
4302
  if @delegate_object[:menu_for_history]
4138
4303
  history_files(
4139
4304
  @dml_link_state,
@@ -4145,8 +4310,8 @@ module MarkdownExec
4145
4310
  dml_menu_append_chrome_item(
4146
4311
  formatted_choice_ostructs[:history].oname, files.count,
4147
4312
  'files',
4148
- id: id,
4149
- menu_state: MenuState::HISTORY
4313
+ menu_state: MenuState::HISTORY,
4314
+ source_id: source_id
4150
4315
  )
4151
4316
  end
4152
4317
  end
@@ -4168,38 +4333,38 @@ module MarkdownExec
4168
4333
  if files.count.positive?
4169
4334
  dml_menu_append_chrome_item(
4170
4335
  formatted_choice_ostructs[:load].dname, files.count, 'files',
4171
- id: "#{id}.load",
4172
- menu_state: MenuState::LOAD
4336
+ menu_state: MenuState::LOAD,
4337
+ source_id: "#{source_id}_vmahf_load"
4173
4338
  )
4174
4339
  end
4175
4340
  if @delegate_object[:menu_inherited_lines_edit_always] ||
4176
4341
  lines_count.positive?
4177
4342
  dml_menu_append_chrome_item(
4178
4343
  formatted_choice_ostructs[:edit].dname, lines_count, 'lines',
4179
- id: "#{id}.edit",
4180
- menu_state: MenuState::EDIT
4344
+ menu_state: MenuState::EDIT,
4345
+ source_id: "#{source_id}_vmahf_edit"
4181
4346
  )
4182
4347
  end
4183
4348
  if lines_count.positive?
4184
4349
  dml_menu_append_chrome_item(
4185
4350
  formatted_choice_ostructs[:save].dname, 1, '',
4186
- id: "#{id}.save",
4187
- menu_state: MenuState::SAVE
4351
+ menu_state: MenuState::SAVE,
4352
+ source_id: "#{source_id}_vmahf_save"
4188
4353
  )
4189
4354
  end
4190
4355
  if lines_count.positive?
4191
4356
  dml_menu_append_chrome_item(
4192
4357
  formatted_choice_ostructs[:view].dname, 1, '',
4193
- id: "#{id}.view",
4194
- menu_state: MenuState::VIEW
4358
+ menu_state: MenuState::VIEW,
4359
+ source_id: "#{source_id}_vmahf_view"
4195
4360
  )
4196
4361
  end
4197
4362
  # rubocop:disable Style/GuardClause
4198
4363
  if @delegate_object[:menu_with_shell]
4199
4364
  dml_menu_append_chrome_item(
4200
4365
  formatted_choice_ostructs[:shell].dname, 1, '',
4201
- id: "#{id}.shell",
4202
- menu_state: MenuState::SHELL
4366
+ menu_state: MenuState::SHELL,
4367
+ source_id: "#{source_id}_vmahf_shell"
4203
4368
  )
4204
4369
  end
4205
4370
  # rubocop:enable Style/GuardClause
@@ -4217,7 +4382,7 @@ module MarkdownExec
4217
4382
  )
4218
4383
  end
4219
4384
 
4220
- def vux_parse_document(id: '')
4385
+ def vux_parse_document(source_id: '')
4221
4386
  @run_state.batch_index += 1
4222
4387
  @run_state.in_own_window = false
4223
4388
 
@@ -4239,7 +4404,9 @@ module MarkdownExec
4239
4404
  # update @delegate_object and @menu_base_options in auto_load
4240
4405
  #
4241
4406
  @dml_blocks_in_file, @dml_menu_blocks, @dml_mdoc =
4242
- mdoc_menu_and_blocks_from_nested_files(@dml_link_state, id: id)
4407
+ mdoc_menu_and_blocks_from_nested_files(
4408
+ @dml_link_state, source_id: source_id
4409
+ )
4243
4410
  dump_delobj(@dml_blocks_in_file, @dml_menu_blocks, @dml_link_state)
4244
4411
  end
4245
4412