markdown_exec 2.6.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -2
  3. data/Gemfile.lock +2 -2
  4. data/Rakefile +36 -0
  5. data/bats/command-substitution.bats +8 -0
  6. data/bats/options.bats +4 -4
  7. data/bats/plain.bats +8 -0
  8. data/bats/table.bats +6 -1
  9. data/bats/variable-expansion.bats +4 -4
  10. data/bin/bmde +46 -2
  11. data/bin/tab_completion.sh +1 -1
  12. data/docs/dev/command-substitution.md +24 -0
  13. data/docs/dev/load_code.md +14 -0
  14. data/docs/dev/no-active-elements.md +6 -0
  15. data/docs/dev/screen-width.md +21 -0
  16. data/docs/dev/table-invalid.md +20 -0
  17. data/examples/bash-blocks.md +2 -1
  18. data/examples/block-names.md +2 -1
  19. data/examples/block-types.md +2 -1
  20. data/examples/data-files.md +1 -0
  21. data/examples/document_options.md +1 -0
  22. data/examples/example-document-opts.md +6 -0
  23. data/examples/index.md +1 -1
  24. data/examples/interrupt.md +1 -0
  25. data/examples/link-blocks-load-save.md +8 -10
  26. data/examples/link-blocks-vars.md +12 -9
  27. data/examples/linked.md +9 -12
  28. data/examples/linked1.md +7 -6
  29. data/examples/linked2.md +6 -7
  30. data/examples/linked3.md +6 -5
  31. data/examples/linked_show.md +5 -4
  32. data/examples/nickname.md +1 -0
  33. data/examples/opts-blocks-require.md +1 -0
  34. data/examples/opts-blocks.md +1 -0
  35. data/examples/opts_output_execution.md +1 -0
  36. data/examples/pass-through-arguments.md +1 -0
  37. data/examples/pause-after-execution.md +1 -0
  38. data/examples/port-blocks.md +14 -10
  39. data/examples/save.md +1 -0
  40. data/examples/text-markup.md +2 -1
  41. data/examples/variable-expansion-save-block.md +48 -0
  42. data/examples/variable-expansion.md +3 -1
  43. data/examples/vars-blocks.md +14 -24
  44. data/examples/wrap.md +2 -1
  45. data/lib/collapser.rb +83 -47
  46. data/lib/evaluate_shell_expressions.rb +3 -2
  47. data/lib/fcb.rb +8 -2
  48. data/lib/hash_delegator.rb +214 -118
  49. data/lib/input_sequencer.rb +0 -7
  50. data/lib/markdown_exec/version.rb +1 -1
  51. data/lib/markdown_exec.rb +6 -1
  52. data/lib/menu.src.yml +22 -9
  53. data/lib/menu.yml +22 -7
  54. data/lib/ww.rb +1 -1
  55. metadata +16 -9
  56. data/docs/dev/table-crash.md +0 -39
  57. data/examples/load_code.md +0 -10
  58. /data/{examples → docs/dev}/load1.sh +0 -0
@@ -264,10 +264,9 @@ module HashDelegatorSelf
264
264
  end
265
265
 
266
266
  # find tables in multiple lines and format horizontally
267
- def tables_into_columns!(blocks_menu, delegate_object)
267
+ def tables_into_columns!(blocks_menu, delegate_object, screen_width_for_table)
268
268
  return unless delegate_object[:tables_into_columns]
269
269
 
270
- screenwidth = delegate_object[:console_width]
271
270
  lines = blocks_menu.map(&:oname)
272
271
  text_tables = TableExtractor.extract_tables(
273
272
  lines,
@@ -301,10 +300,10 @@ module HashDelegatorSelf
301
300
  # replace text in each block
302
301
  range.each.with_index do |block_ind, ind|
303
302
  fcb = blocks_menu[block_ind]
304
- fcb.s3formatted_table_row = fcb.padded = table__hs[ind] ####
303
+ fcb.s3formatted_table_row = fcb.padded = table__hs[ind]
305
304
  fcb.padded_width = table__hs[ind].padded_width
306
305
  if fcb.center
307
- cw = (screenwidth - table__hs[ind].padded_width) / 2
306
+ cw = (screen_width_for_table - table__hs[ind].padded_width) / 2
308
307
  if cw.positive?
309
308
  indent = ' ' * cw
310
309
  fcb.s3indent = fcb.indent = indent
@@ -321,7 +320,7 @@ module HashDelegatorSelf
321
320
  # s0printable: line_obj[:text],
322
321
  # s1decorated: decorated,
323
322
  # s2title = fcb.oname
324
- # s3formatted_table_row = fcb.padded = table__hs[ind]####
323
+ # s3formatted_table_row = fcb.padded = table__hs[ind]
325
324
 
326
325
  # Creates a TTY prompt with custom settings. Specifically,
327
326
  # it disables the default 'cross' symbol and
@@ -642,8 +641,6 @@ module MarkdownExec
642
641
  position: :final)
643
642
  end
644
643
 
645
- public
646
-
647
644
  # Appends a chrome block, which is a menu option for Back or Exit
648
645
  #
649
646
  # @param all_blocks [Array] The current blocks in the menu
@@ -776,7 +773,7 @@ module MarkdownExec
776
773
  end
777
774
 
778
775
  def assign_key_value_in_bash(key, value)
779
- if value =~ /["$\\`]/
776
+ if value.to_s =~ /["$\\`]/
780
777
  # requiring ShellWords to write into Bash scripts
781
778
  "#{key}=#{Shellwords.escape(value)}"
782
779
  else
@@ -791,7 +788,9 @@ module MarkdownExec
791
788
  # The method categorizes blocks based on their type and processes them accordingly.
792
789
  #
793
790
  # @return [Array<FCB>] An array of FCB objects representing the blocks.
794
- def blocks_from_nested_files
791
+ def blocks_from_nested_files(
792
+ link_state: @dml_link_state || LinkState.new
793
+ )
795
794
  register_console_attributes(@delegate_object)
796
795
  @decor_patterns_from_delegate_object_for_block_create = collect_line_decor_patterns(@delegate_object)
797
796
 
@@ -799,6 +798,23 @@ module MarkdownExec
799
798
  blocks = []
800
799
  iter_blocks_from_nested_files do |btype, fcb|
801
800
  count += 1
801
+
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
+
802
818
  case btype
803
819
  when :blocks
804
820
  if @delegate_object[:bash]
@@ -806,16 +822,18 @@ module MarkdownExec
806
822
  block_calls_scan: @delegate_object[:block_calls_scan],
807
823
  block_name_match: @delegate_object[:block_name_match],
808
824
  block_name_nick_match: @delegate_object[:block_name_nick_match],
809
- id: "*#{count}",
825
+ id: "*#{count}"
810
826
  ) do |oname, color|
811
827
  apply_block_type_color_option(oname, color)
812
828
  end
813
829
  end
830
+ expand_references.call(fcb)
814
831
  blocks << fcb
815
832
  when :filter # types accepted
816
833
  %i[blocks line]
817
834
  when :line
818
835
  unless @delegate_object[:no_chrome]
836
+ expand_references.call(fcb)
819
837
  create_and_add_chrome_blocks(blocks, fcb, id: "*#{count}",
820
838
  init_ids: init_ids)
821
839
  end
@@ -853,10 +871,12 @@ module MarkdownExec
853
871
 
854
872
  # private
855
873
 
856
- def build_replacement_dictionary(commands, link_state)
874
+ def build_replacement_dictionary(commands, link_state,
875
+ initial_code_required: false, key_format:)
857
876
  evaluate_shell_expressions(
858
- link_state.inherited_lines_block, commands,
859
- key_format: "${%s}" # no need to escape variable name for regexp
877
+ (link_state&.inherited_lines_block || ''), commands,
878
+ initial_code_required: initial_code_required,
879
+ key_format: key_format
860
880
  ) # !!t
861
881
  end
862
882
 
@@ -1022,9 +1042,7 @@ module MarkdownExec
1022
1042
  end
1023
1043
 
1024
1044
  def command_execute_in_own_window_format_arguments(
1025
- home: Dir.pwd,
1026
- erls:,
1027
- rest: ''
1045
+ erls:, home: Dir.pwd, rest: ''
1028
1046
  )
1029
1047
  {
1030
1048
  batch_index: @run_state.batch_index,
@@ -1145,19 +1163,21 @@ module MarkdownExec
1145
1163
  end
1146
1164
 
1147
1165
  def count_named_group_occurrences(
1148
- blocks, pattern, exclude_types: [BlockType::SHELL]
1166
+ blocks, pattern, exclude_types: [BlockType::SHELL],
1167
+ group_name:
1149
1168
  )
1150
1169
  # Initialize a counter for named group occurrences
1151
1170
  occurrence_count = Hash.new(0)
1171
+ return occurrence_count if pattern == //
1152
1172
 
1153
1173
  blocks.each do |block|
1154
1174
  # Skip processing for shell-type blocks
1155
1175
  next if exclude_types.include?(block.type)
1156
1176
 
1157
1177
  # Scan each block name for matches of the pattern
1158
- block.oname.scan(pattern) do |(_, variable_name)|
1178
+ ([block.oname || ''] + block.body).join("\n").scan(pattern) do |(_, _variable_name)|
1159
1179
  pattern.match($LAST_MATCH_INFO.to_s) # Reapply match for named groups
1160
- occurrence_count[$LAST_MATCH_INFO[:variable]] += 1
1180
+ occurrence_count[$LAST_MATCH_INFO[group_name]] += 1
1161
1181
  end
1162
1182
  end
1163
1183
 
@@ -1175,18 +1195,14 @@ module MarkdownExec
1175
1195
  # @param color_method [Symbol] The color method to apply
1176
1196
  # to the block's display name.
1177
1197
  # return number of lines added
1178
- def create_and_add_chrome_block(blocks:, match_data:,
1179
- collapse: nil,
1180
- format_option:, color_method:,
1181
- case_conversion: nil,
1182
- center: nil,
1183
- decor_patterns: [],
1184
- disabled: true,
1185
- id: '',
1186
- level: 0,
1187
- type: '',
1188
- wrap: nil)
1189
- line_cap = NamedCaptureExtractor::extract_named_group2(match_data)
1198
+ def create_and_add_chrome_block(
1199
+ blocks:, case_conversion: nil, center: nil,
1200
+ collapse: nil, color_method:, decor_patterns: [],
1201
+ disabled: true, format_option:, id: '',
1202
+ level: 0, match_data:, type: '',
1203
+ wrap: nil
1204
+ )
1205
+ line_cap = NamedCaptureExtractor.extract_named_group2(match_data)
1190
1206
  # replace tabs in indent
1191
1207
  line_cap[:indent] ||= ''
1192
1208
  line_cap[:indent] = line_cap[:indent].dup if line_cap[:indent].frozen?
@@ -1199,11 +1215,9 @@ module MarkdownExec
1199
1215
  line_cap[:collapse] ||= ''
1200
1216
  line_cap[:line] ||= ''
1201
1217
 
1202
- accepted_width = @delegate_object[:console_width] - 2
1203
-
1204
1218
  line_caps = [line_cap]
1205
- if wrap && line_cap[:text].length > accepted_width
1206
- wrapper = StringWrapper.new(width: accepted_width - line_cap[:indent].length)
1219
+ if wrap && line_cap[:text].length > screen_width_for_wrapping
1220
+ wrapper = StringWrapper.new(width: screen_width_for_wrapping - line_cap[:indent].length)
1207
1221
  line_caps = wrapper.wrap(line_cap[:text]).map do |wrapped_line|
1208
1222
  line_cap.dup.merge(text: wrapped_line)
1209
1223
  end
@@ -1212,8 +1226,8 @@ module MarkdownExec
1212
1226
  if center
1213
1227
  line_caps.each do |line_obj|
1214
1228
  line_obj[:indent] =
1215
- if line_obj[:text].length < accepted_width
1216
- ' ' * ((accepted_width - line_obj[:text].length) / 2)
1229
+ if line_obj[:text].length < screen_width_for_wrapping
1230
+ ' ' * ((screen_width_for_wrapping - line_obj[:text].length) / 2)
1217
1231
  else
1218
1232
  ''
1219
1233
  end
@@ -1248,8 +1262,6 @@ module MarkdownExec
1248
1262
  collapse: collapse.nil? ? (line_obj[:collapse] == COLLAPSIBLE_TOKEN_COLLAPSE) : collapse,
1249
1263
  token: line_obj[:collapse],
1250
1264
  disabled: disabled ? TtyMenu::DISABLE : nil,
1251
- ####
1252
- # id: "#{@delegate_object[:filename]}:#{index}",
1253
1265
  id: "#{id}.#{index}",
1254
1266
  level: level,
1255
1267
  s0indent: indent,
@@ -1292,7 +1304,7 @@ module MarkdownExec
1292
1304
  when COLLAPSIBLE_TOKEN_EXPAND
1293
1305
  false
1294
1306
  else
1295
- false####
1307
+ false
1296
1308
  end,
1297
1309
 
1298
1310
  color_method: criteria[:color] &&
@@ -1864,6 +1876,7 @@ module MarkdownExec
1864
1876
  exit_prompt: @delegate_object[:prompt_filespec_back],
1865
1877
  filename_pattern: @delegate_object[:vars_block_filename_pattern],
1866
1878
  glob: @delegate_object[:document_configurations_glob],
1879
+ menu_options: HashDelegator.options_for_tty_menu(@delegate_object),
1867
1880
  view: @delegate_object[:vars_block_filename_view]
1868
1881
  )
1869
1882
  block_data = HashDelegator.parse_yaml_data_from_body(selected.body)
@@ -1878,7 +1891,7 @@ module MarkdownExec
1878
1891
  ).sort.map do |file|
1879
1892
  { name: format(
1880
1893
  block_data['view'] || view,
1881
- NamedCaptureExtractor::extract_named_group2(
1894
+ NamedCaptureExtractor.extract_named_group2(
1882
1895
  file.match(
1883
1896
  Regexp.new(block_data['filename_pattern'] ||
1884
1897
  filename_pattern)
@@ -1887,13 +1900,15 @@ module MarkdownExec
1887
1900
  ),
1888
1901
  oname: file }
1889
1902
  end,
1890
- simple_menu_options
1903
+ menu_options.merge(
1904
+ cycle: true
1905
+ )
1891
1906
  )
1892
1907
  if selected_option.dname != exit_prompt
1893
1908
  File.readlines(selected_option.oname, chomp: true)
1894
1909
  end
1895
1910
  else
1896
- warn "No matching files found" ###
1911
+ warn 'No matching files found'
1897
1912
  end
1898
1913
  end
1899
1914
 
@@ -1964,7 +1979,7 @@ module MarkdownExec
1964
1979
  end
1965
1980
 
1966
1981
  save_filespec_from_expression(directory_glob).tap do |save_filespec|
1967
- if save_filespec && save != exit_prompt
1982
+ if save_filespec && save_filespec != exit_prompt
1968
1983
  begin
1969
1984
  File.write(save_filespec,
1970
1985
  HashDelegator.join_code_lines(code_lines))
@@ -2140,7 +2155,9 @@ module MarkdownExec
2140
2155
  )
2141
2156
  # update blocks
2142
2157
  #
2143
- Regexp.union(replacements.keys).tap do |pattern|
2158
+ Regexp.union(replacements.keys.map do |word|
2159
+ Regexp.new(Regexp.escape(word))
2160
+ end).tap do |pattern|
2144
2161
  menu_blocks.each do |block|
2145
2162
  next if exclude_types.include?(block.type)
2146
2163
 
@@ -2150,25 +2167,31 @@ module MarkdownExec
2150
2167
  end
2151
2168
 
2152
2169
  def expand_variable_references!(
2153
- # echo_format: 'echo "$%s"',
2154
2170
  echo_format: 'echo $%s',
2155
2171
  link_state:,
2156
2172
  blocks:,
2157
- pattern: Regexp.new(@delegate_object[:variable_expression_regexp])
2173
+ group_name: :variable,
2174
+ initial_code_required: false,
2175
+ key_format: '${%s}',
2176
+ pattern: nil
2158
2177
  )
2159
- # Count occurrences of named groups in each block
2160
- variable_counts = count_named_group_occurrences(blocks, pattern)
2178
+ pattern ||= options_variable_expression_regexp
2179
+ return if pattern.nil?
2180
+
2181
+ variable_counts = count_named_group_occurrences(blocks, pattern,
2182
+ group_name: group_name)
2183
+ return if variable_counts.nil?
2161
2184
 
2162
- # Generate echo commands for each variable based on its count
2163
2185
  echo_commands = generate_echo_commands(variable_counts, echo_format)
2164
2186
 
2165
- # Build a dictionary to replace variables with the corresponding commands
2166
- replacements = build_replacement_dictionary(echo_commands, link_state)
2187
+ replacements = build_replacement_dictionary(
2188
+ echo_commands, link_state,
2189
+ initial_code_required: initial_code_required,
2190
+ key_format: key_format
2191
+ )
2167
2192
 
2168
- # Exit early if no replacements are needed
2169
2193
  return if replacements.nil?
2170
2194
 
2171
- # Expand each block with replacements from the dictionary
2172
2195
  expand_blocks_with_replacements(blocks, replacements)
2173
2196
  end
2174
2197
 
@@ -2284,7 +2307,7 @@ module MarkdownExec
2284
2307
  # commands to echo variables
2285
2308
  #
2286
2309
  commands = {}
2287
- variable_counts.each do |variable, count|
2310
+ variable_counts.each do |variable, _count|
2288
2311
  command = format(echo_format, variable)
2289
2312
  commands[variable] = command
2290
2313
  end
@@ -2380,8 +2403,6 @@ module MarkdownExec
2380
2403
  }
2381
2404
  end
2382
2405
 
2383
- public
2384
-
2385
2406
  # Iterates through blocks in a file, applying the provided block to each line.
2386
2407
  # The iteration only occurs if the file exists.
2387
2408
  # @yield [Symbol] :filter Yields to obtain selected messages for processing.
@@ -2392,15 +2413,15 @@ module MarkdownExec
2392
2413
  selected_types = yield :filter
2393
2414
  cfile.readlines(
2394
2415
  @delegate_object[:filename],
2395
- import_paths: @delegate_object[:import_paths]&.split(':')
2416
+ import_paths: options_import_paths
2396
2417
  ).each_with_index do |nested_line, index|
2397
- if nested_line
2398
- update_line_and_block_state(
2399
- nested_line, state, selected_types,
2400
- id: "#{@delegate_object[:filename]}:#{index}",
2401
- &block
2402
- )
2403
- end
2418
+ next unless nested_line
2419
+
2420
+ update_line_and_block_state(
2421
+ nested_line, state, selected_types,
2422
+ id: "#{@delegate_object[:filename]}:#{index}",
2423
+ &block
2424
+ )
2404
2425
  end
2405
2426
  end
2406
2427
 
@@ -2580,7 +2601,7 @@ module MarkdownExec
2580
2601
  end
2581
2602
 
2582
2603
  def load_cli_or_user_selected_block(all_blocks: [], menu_blocks: [],
2583
- default: nil)
2604
+ prior_answer: nil)
2584
2605
  if @delegate_object[:block_name].present?
2585
2606
  block = all_blocks.find do |item|
2586
2607
  item.pub_name == @delegate_object[:block_name]
@@ -2588,7 +2609,7 @@ module MarkdownExec
2588
2609
  source = OpenStruct.new(block_name_from_ui: false)
2589
2610
  else
2590
2611
  block_state = wait_for_user_selected_block(all_blocks, menu_blocks,
2591
- default)
2612
+ prior_answer)
2592
2613
  return if block_state.nil?
2593
2614
 
2594
2615
  block = block_state.block
@@ -2693,11 +2714,6 @@ module MarkdownExec
2693
2714
  @delegate_object.merge!(compressed_ids: @compressed_ids)
2694
2715
  )
2695
2716
 
2696
- # text substitution in menu
2697
- #
2698
- expand_variable_references!(blocks: menu_blocks, link_state: link_state)
2699
- # expand_command_substition!(blocks: menu_blocks, link_state: link_state)
2700
-
2701
2717
  # chrome for menu
2702
2718
  #
2703
2719
  add_menu_chrome_blocks!(id: id, menu_blocks: menu_blocks,
@@ -2705,7 +2721,8 @@ module MarkdownExec
2705
2721
 
2706
2722
  ### compress empty lines
2707
2723
  HashDelegator.delete_consecutive_blank_lines!(menu_blocks)
2708
- HashDelegator.tables_into_columns!(menu_blocks, @delegate_object) ####
2724
+ HashDelegator.tables_into_columns!(menu_blocks, @delegate_object,
2725
+ screen_width_for_table)
2709
2726
 
2710
2727
  [all_blocks, menu_blocks, mdoc]
2711
2728
  end
@@ -2805,6 +2822,19 @@ module MarkdownExec
2805
2822
  )
2806
2823
  end
2807
2824
 
2825
+ def options_command_substitution_regexp
2826
+ Regexp.new(@delegate_object[:command_substitution_regexp] || '')
2827
+ end
2828
+
2829
+ def options_import_paths
2830
+ @delegate_object[:import_paths]&.split(':') || ''
2831
+ end
2832
+
2833
+ def options_variable_expression_regexp
2834
+ @delegate_object[:variable_expression_regexp].present? &&
2835
+ Regexp.new(@delegate_object[:variable_expression_regexp])
2836
+ end
2837
+
2808
2838
  def output_color_formatted(data_sym, color_sym)
2809
2839
  formatted_string = string_send_color(@delegate_object[data_sym],
2810
2840
  color_sym)
@@ -2927,9 +2957,24 @@ module MarkdownExec
2927
2957
  # @param opts [Hash] The options hash.
2928
2958
  # @return [Array<Hash>] The updated blocks menu.
2929
2959
  def blocks_as_menu_items(menu_blocks)
2960
+ # prefix first active line, inactive for rest
2961
+ active = @delegate_object[:prompt_margin_left_text]
2962
+ inactive = ' ' * active.length
2963
+
2930
2964
  select_blocks(menu_blocks).map do |fcb|
2931
- fcb.name = fcb.indented_decorated || (fcb.indent + (fcb.s1decorated || fcb.dname))
2932
- fcb.value = fcb.id || fcb.name
2965
+ multiline = fcb.indented_decorated ||
2966
+ (fcb.indent + (fcb.s1decorated || fcb.dname))
2967
+
2968
+ fcb.name = multiline.each_line.with_index.map do |line, index|
2969
+ if fcb.fetch(:disabled, nil).nil?
2970
+ index.zero? ? active : inactive
2971
+ else
2972
+ inactive
2973
+ end + line.chomp
2974
+ end.join("\n")
2975
+
2976
+ fcb.value = fcb.id || fcb.name.split("\n").first
2977
+
2933
2978
  fcb.to_h
2934
2979
  end
2935
2980
  end
@@ -2953,7 +2998,7 @@ module MarkdownExec
2953
2998
  in_block = false
2954
2999
  elsif scan1.present?
2955
3000
  if format1.present?
2956
- caps = NamedCaptureExtractor::extract_named_groups(line, scan1)
3001
+ caps = NamedCaptureExtractor.extract_named_groups(line, scan1)
2957
3002
  if caps
2958
3003
  formatted = format(format1, caps)
2959
3004
  collected_lines << formatted
@@ -3078,6 +3123,18 @@ module MarkdownExec
3078
3123
  sel == MenuOptions::YES
3079
3124
  end
3080
3125
 
3126
+ def prompt_margin_left_text
3127
+ @delegate_object[:prompt_margin_left_text]
3128
+ end
3129
+
3130
+ def prompt_margin_left_width
3131
+ prompt_margin_left_text.length
3132
+ end
3133
+
3134
+ def prompt_margin_right_width
3135
+ 0
3136
+ end
3137
+
3081
3138
  # public
3082
3139
 
3083
3140
  def prompt_select_code_filename(
@@ -3172,8 +3229,11 @@ module MarkdownExec
3172
3229
  history_files(
3173
3230
  @dml_link_state,
3174
3231
  filename:
3175
- asset.present? ? saved_asset_filename(asset,
3176
- @dml_link_state) : filename,
3232
+ if asset.present?
3233
+ saved_asset_filename(asset, @dml_link_state)
3234
+ else
3235
+ filename
3236
+ end,
3177
3237
  path: path
3178
3238
  )&.map do |file|
3179
3239
  unless Regexp.new(regexp) =~ file
@@ -3192,24 +3252,23 @@ module MarkdownExec
3192
3252
  def saved_asset_for_history(
3193
3253
  file:, form:, match_info:
3194
3254
  )
3195
- begin
3196
- OpenStruct.new(
3197
- file: file[(Dir.pwd.length + 1)..-1],
3198
- full: file,
3199
- row: format(
3200
- form,
3201
- # default '*' so unknown parameters are given a wildcard
3202
- match_info.names.each_with_object(Hash.new('*')) do |name, hash|
3203
- hash[name.to_sym] = match_info[name]
3204
- end
3205
- )
3255
+
3256
+ OpenStruct.new(
3257
+ file: file[(Dir.pwd.length + 1)..-1],
3258
+ full: file,
3259
+ row: format(
3260
+ form,
3261
+ # default '*' so unknown parameters are given a wildcard
3262
+ match_info.names.each_with_object(Hash.new('*')) do |name, hash|
3263
+ hash[name.to_sym] = match_info[name]
3264
+ end
3206
3265
  )
3207
- rescue KeyError
3208
- # pp $!, $@
3209
- warn "Cannot format with: #{@delegate_object[:saved_history_format]}"
3210
- error_handler('saved_history_format')
3211
- return :break
3212
- end
3266
+ )
3267
+ rescue KeyError
3268
+ # pp $!, $@
3269
+ warn "Cannot format with: #{@delegate_object[:saved_history_format]}"
3270
+ error_handler('saved_history_format')
3271
+ :break
3213
3272
  end
3214
3273
 
3215
3274
  # Processes YAML data from the selected menu item, updating delegate
@@ -3282,6 +3341,8 @@ module MarkdownExec
3282
3341
  # # opts will be updated with the current console dimensions
3283
3342
  # # and pagination settings.
3284
3343
  def register_console_attributes(opts)
3344
+ return unless IO.console
3345
+
3285
3346
  if (resized = @delegate_object[:menu_resize_terminal])
3286
3347
  resize_terminal
3287
3348
  end
@@ -3305,9 +3366,9 @@ module MarkdownExec
3305
3366
 
3306
3367
  def replace_keys_in_lines(replacement_dictionary, lines)
3307
3368
  # Create a regex pattern that matches any key in the replacement dictionary
3308
- pattern = Regexp.union(replacement_dictionary.keys.map { |key|
3369
+ pattern = Regexp.union(replacement_dictionary.keys.map do |key|
3309
3370
  "%<#{key}>"
3310
- })
3371
+ end)
3311
3372
 
3312
3373
  # Iterate over each line and apply gsub with the replacement hash
3313
3374
  lines.map do |line|
@@ -3429,8 +3490,24 @@ module MarkdownExec
3429
3490
  ).generate_name
3430
3491
  end
3431
3492
 
3493
+ def screen_width
3494
+ if @delegate_object[:screen_width] && @delegate_object[:screen_width].positive?
3495
+ @delegate_object[:screen_width]
3496
+ else
3497
+ @delegate_object[:console_width]
3498
+ end
3499
+ end
3500
+
3501
+ def screen_width_for_table
3502
+ screen_width - prompt_margin_left_width - prompt_margin_right_width - 2 # prompt is symbol + space (width: 2)
3503
+ end
3504
+
3505
+ def screen_width_for_wrapping
3506
+ screen_width_for_table
3507
+ end
3508
+
3432
3509
  def select_document_if_multiple(options, files, prompt:)
3433
- return files if files.class == String ###
3510
+ return files if files.class == String
3434
3511
  return files[0] if (count = files.count) == 1
3435
3512
 
3436
3513
  return unless count >= 2
@@ -3463,9 +3540,20 @@ module MarkdownExec
3463
3540
  props = {
3464
3541
  active_color: active_color_pastel.detach,
3465
3542
  # activate dynamic list searching on letter/number key presses
3543
+ cycle: true,
3466
3544
  filter: true,
3545
+ per_page: @delegate_object[:select_page_height]
3467
3546
  }.freeze
3468
3547
 
3548
+ if menu_items.all? do |item|
3549
+ !item.is_a?(String) && item[:disabled]
3550
+ end
3551
+ menu_items.each do |prompt_item|
3552
+ puts prompt_item[:dname]
3553
+ end
3554
+ return
3555
+ end
3556
+
3469
3557
  # crashes if all menu options are disabled
3470
3558
  # crashes if default is not an existing item
3471
3559
  #
@@ -3484,7 +3572,6 @@ module MarkdownExec
3484
3572
 
3485
3573
  selected = menu_items.find do |item|
3486
3574
  if item.instance_of?(Hash)
3487
- # (item[:id] || item[:name] || item[:dname]) == selection
3488
3575
  [item[:id], item[:name], item[:dname]].include?(selection)
3489
3576
  elsif item.instance_of?(MarkdownExec::FCB)
3490
3577
  item.dname == selection || item.id == selection
@@ -3555,12 +3642,7 @@ module MarkdownExec
3555
3642
  menu_with_back && @link_history.prior_state_exist?
3556
3643
  end
3557
3644
 
3558
- def simple_menu_options(
3559
- per_page: @delegate_object[:select_page_height]
3560
- )
3561
- { cycle: true,
3562
- per_page: per_page }
3563
- end
3645
+ def simple_menu_options; end
3564
3646
 
3565
3647
  # Initializes a new fenced code block (FCB) object based
3566
3648
  # on the provided line and heading information.
@@ -3570,7 +3652,7 @@ module MarkdownExec
3570
3652
  # Regular expression to identify fenced block start.
3571
3653
  # @return [MarkdownExec::FCB] A new FCB instance with the parsed attributes.
3572
3654
  def start_fenced_block(line, headings, fenced_start_extended_regex)
3573
- fcb_title_groups = NamedCaptureExtractor::extract_named_groups(
3655
+ fcb_title_groups = NamedCaptureExtractor.extract_named_groups(
3574
3656
  line, fenced_start_extended_regex
3575
3657
  )
3576
3658
 
@@ -3627,10 +3709,10 @@ module MarkdownExec
3627
3709
  shell: fcb_title_groups.fetch(:shell, ''),
3628
3710
  start_line: line,
3629
3711
  stdin: if (tn = rest.match(/<(?<type>\$)?(?<name>[A-Za-z_-]\S+)/))
3630
- NamedCaptureExtractor::extract_named_group2(tn)
3712
+ NamedCaptureExtractor.extract_named_group2(tn)
3631
3713
  end,
3632
3714
  stdout: if (tn = rest.match(/>(?<type>\$)?(?<name>[\w.\-]+)/))
3633
- NamedCaptureExtractor::extract_named_group2(tn)
3715
+ NamedCaptureExtractor.extract_named_group2(tn)
3634
3716
  end,
3635
3717
  title: title,
3636
3718
  type: fcb_title_groups.fetch(:type, ''),
@@ -3736,7 +3818,7 @@ module MarkdownExec
3736
3818
  @dml_block_state = load_cli_or_user_selected_block(
3737
3819
  all_blocks: @dml_blocks_in_file,
3738
3820
  menu_blocks: @dml_menu_blocks,
3739
- default: @dml_menu_default_dname
3821
+ prior_answer: @dml_menu_default_dname
3740
3822
  )
3741
3823
  if !@dml_block_state
3742
3824
  # HashDelegator.error_handler('block_state missing', { abort: true })
@@ -4004,7 +4086,7 @@ module MarkdownExec
4004
4086
  when :parse_document # once for each menu
4005
4087
  vux_parse_document(id: 'vux_parse_document')
4006
4088
  vux_menu_append_history_files(formatted_choice_ostructs,
4007
- id: "vux_menu_append_history_files",)
4089
+ id: 'vux_menu_append_history_files')
4008
4090
  vux_publish_document_file_name_for_external_automation
4009
4091
 
4010
4092
  when :display_menu
@@ -4156,7 +4238,6 @@ module MarkdownExec
4156
4238
 
4157
4239
  # update @delegate_object and @menu_base_options in auto_load
4158
4240
  #
4159
- # @dml_blocks_in_file, @dml_menu_blocks, @dml_mdoc, @dml_link_state =
4160
4241
  @dml_blocks_in_file, @dml_menu_blocks, @dml_mdoc =
4161
4242
  mdoc_menu_and_blocks_from_nested_files(@dml_link_state, id: id)
4162
4243
  dump_delobj(@dml_blocks_in_file, @dml_menu_blocks, @dml_link_state)
@@ -4220,13 +4301,14 @@ module MarkdownExec
4220
4301
  # user interrupts process
4221
4302
  end
4222
4303
 
4223
- def wait_for_user_selected_block(all_blocks, menu_blocks, default)
4224
- block_state = wait_for_user_selection(all_blocks, menu_blocks, default)
4304
+ def wait_for_user_selected_block(all_blocks, menu_blocks, prior_answer)
4305
+ block_state = wait_for_user_selection(all_blocks, menu_blocks,
4306
+ prior_answer)
4225
4307
  handle_back_or_continue(block_state)
4226
4308
  block_state
4227
4309
  end
4228
4310
 
4229
- def wait_for_user_selection(_all_blocks, menu_blocks, default)
4311
+ def wait_for_user_selection(_all_blocks, menu_blocks, prior_answer)
4230
4312
  if @delegate_object[:clear_screen_for_select_block]
4231
4313
  printf("\e[1;1H\e[2J")
4232
4314
  end
@@ -4242,10 +4324,20 @@ module MarkdownExec
4242
4324
  MenuState::EXIT)
4243
4325
  end
4244
4326
 
4245
- # default value may not match if color is different from
4327
+ selected_answer = case prior_answer
4328
+ when nil
4329
+ nil
4330
+ when String
4331
+ menu_blocks.find { |block|
4332
+ block.dname.include?(prior_answer)
4333
+ }&.name
4334
+ when Struct
4335
+ prior_answer.index || prior_answer.name
4336
+ end
4337
+ # prior_answer value may not match if color is different from
4246
4338
  # originating menu (opts changed while processing)
4247
- selection_opts = if default && menu_blocks.map(&:dname).include?(default)
4248
- @delegate_object.merge(default: default)
4339
+ selection_opts = if selected_answer
4340
+ @delegate_object.merge(default: selected_answer)
4249
4341
  else
4250
4342
  @delegate_object
4251
4343
  end
@@ -4374,6 +4466,10 @@ module MarkdownExec
4374
4466
  def self.next_link_state(*args, **kwargs, &block)
4375
4467
  super
4376
4468
  end
4469
+
4470
+ def self.options_for_tty_menu(options)
4471
+ options.slice(:menu_active_color_pastel_messages, :select_page_height)
4472
+ end
4377
4473
  end
4378
4474
  end
4379
4475
 
@@ -5135,7 +5231,7 @@ module MarkdownExec
5135
5231
 
5136
5232
  def test_iter_blocks_from_nested_files
5137
5233
  @hd.cfile.expect(:readlines, ['line 1', 'line 2'], ['test.md'],
5138
- import_paths: nil)
5234
+ import_paths: '')
5139
5235
  selected_types = ['filtered message']
5140
5236
 
5141
5237
  result = @hd.iter_blocks_from_nested_files { selected_types }