markdown_exec 2.7.1 → 2.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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