markdown_exec 2.8.3 → 2.8.4

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/Gemfile.lock +1 -1
  4. data/Rakefile +33 -23
  5. data/bats/{block-types.bats → block-type-bash.bats} +0 -25
  6. data/bats/block-type-link.bats +9 -0
  7. data/bats/block-type-port.bats +16 -0
  8. data/bats/block-type-ux-allowed.bats +29 -0
  9. data/bats/block-type-ux-auto.bats +1 -1
  10. data/bats/block-type-ux-chained.bats +9 -0
  11. data/bats/block-type-ux-echo-hash.bats +14 -0
  12. data/bats/block-type-ux-echo.bats +2 -2
  13. data/bats/block-type-ux-exec.bats +1 -1
  14. data/bats/block-type-ux-hidden.bats +9 -0
  15. data/bats/block-type-ux-invalid.bats +8 -0
  16. data/bats/block-type-ux-transform.bats +1 -1
  17. data/bats/indented-block-type-vars.bats +9 -0
  18. data/bats/line-decor-dynamic.bats +1 -1
  19. data/bats/test_helper.bash +9 -2
  20. data/bats/variable-expansion-multiline.bats +8 -0
  21. data/bats/variable-expansion.bats +1 -1
  22. data/docs/dev/block-type-ux-allowed.md +80 -0
  23. data/docs/dev/block-type-ux-chained.md +21 -0
  24. data/docs/dev/block-type-ux-echo-hash.md +72 -0
  25. data/docs/dev/block-type-ux-hidden.md +21 -0
  26. data/docs/dev/block-type-ux-invalid.md +5 -0
  27. data/docs/dev/indented-block-type-vars.md +6 -0
  28. data/docs/dev/line-decor-dynamic.md +2 -1
  29. data/docs/dev/variable-expansion-multiline.md +31 -0
  30. data/lib/ansi_formatter.rb +0 -1
  31. data/lib/ansi_string.rb +1 -1
  32. data/lib/array.rb +0 -1
  33. data/lib/array_util.rb +0 -1
  34. data/lib/block_label.rb +1 -1
  35. data/lib/cached_nested_file_reader.rb +1 -1
  36. data/lib/constants.rb +18 -0
  37. data/lib/directory_searcher.rb +1 -1
  38. data/lib/exceptions.rb +0 -1
  39. data/lib/fcb.rb +36 -5
  40. data/lib/filter.rb +1 -2
  41. data/lib/format_table.rb +1 -0
  42. data/lib/fout.rb +1 -1
  43. data/lib/hash.rb +0 -1
  44. data/lib/hash_delegator.rb +310 -162
  45. data/lib/link_history.rb +17 -17
  46. data/lib/logged_struct.rb +429 -0
  47. data/lib/markdown_exec/version.rb +1 -1
  48. data/lib/markdown_exec.rb +3 -3
  49. data/lib/mdoc.rb +5 -17
  50. data/lib/menu.src.yml +3 -1
  51. data/lib/menu.yml +1 -1
  52. data/lib/namer.rb +4 -5
  53. data/lib/null_result.rb +131 -0
  54. data/lib/object_present.rb +1 -1
  55. data/lib/option_value.rb +1 -1
  56. data/lib/resize_terminal.rb +1 -2
  57. data/lib/saved_assets.rb +1 -1
  58. data/lib/saved_files_matcher.rb +1 -1
  59. data/lib/shell_session.rb +439 -0
  60. data/lib/streams_out.rb +0 -1
  61. data/lib/string_util.rb +11 -1
  62. data/lib/success_result.rb +112 -0
  63. data/lib/text_analyzer.rb +1 -0
  64. data/lib/ww.rb +9 -7
  65. metadata +23 -3
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bundle exec ruby
1
+ #!/usr/bin/env -S bundle exec ruby
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # encoding=utf-8
@@ -45,14 +45,6 @@ require_relative 'text_analyzer'
45
45
  $pd = false unless defined?($pd)
46
46
  $table_cell_truncate = true
47
47
 
48
- class String
49
- # Checks if the string is not empty.
50
- # @return [Boolean] Returns true if the string is not empty, false otherwise.
51
- def non_empty?
52
- !empty?
53
- end
54
- end
55
-
56
48
  module HashDelegatorSelf
57
49
  # Applies an ANSI color method to a string using a specified color key.
58
50
  # The method retrieves the color method from the provided hash. If the
@@ -166,7 +158,7 @@ module HashDelegatorSelf
166
158
  # (default is an empty string).
167
159
  # @return [String] A single string with each line indented as specified.
168
160
  def indent_all_lines(body, indent = nil)
169
- return body unless indent&.non_empty?
161
+ return body unless indent&.present?
170
162
 
171
163
  body.lines.map { |line| indent + line.chomp }.join("\n")
172
164
  end
@@ -862,12 +854,15 @@ module MarkdownExec
862
854
 
863
855
  count = 0
864
856
  blocks = []
857
+ results = {}
865
858
  iter_blocks_from_nested_files do |btype, fcb|
866
859
  count += 1
867
860
  case btype
868
861
  when :blocks
862
+ result = SuccessResult.instance
869
863
  if @delegate_object[:bash]
870
- fcb.for_menu!(
864
+ # prepare block for menu, may fail and call HashDelegator.error_handler
865
+ result = fcb.for_menu!(
871
866
  block_calls_scan: @delegate_object[:block_calls_scan],
872
867
  block_name_match: @delegate_object[:block_name_match],
873
868
  block_name_nick_match: @delegate_object[:block_name_nick_match],
@@ -878,10 +873,11 @@ module MarkdownExec
878
873
  ) do |oname, color|
879
874
  apply_block_type_color_option(oname, color)
880
875
  end
876
+ results[fcb.id] = result if result.failure?
881
877
  else
882
878
  expand_references!(fcb, link_state)
883
879
  end
884
- blocks << fcb
880
+ blocks << fcb unless result.failure?
885
881
  when :filter # types accepted
886
882
  %i[blocks line]
887
883
  when :line
@@ -896,9 +892,9 @@ module MarkdownExec
896
892
  end
897
893
  end
898
894
  end
899
- # !!t blocks.count
900
- blocks
895
+ OpenStruct.new(blocks: blocks, results: results)
901
896
  rescue StandardError
897
+ # ww $@, $!,
902
898
  HashDelegator.error_handler('blocks_from_nested_files')
903
899
  end
904
900
 
@@ -914,7 +910,8 @@ module MarkdownExec
914
910
  initial_code_required: false, key_format:
915
911
  )
916
912
  evaluate_shell_expressions(
917
- (link_state&.inherited_lines_block || ''), commands,
913
+ (link_state&.inherited_lines_block || ''),
914
+ commands,
918
915
  initial_code_required: initial_code_required,
919
916
  key_format: key_format
920
917
  )
@@ -990,6 +987,21 @@ module MarkdownExec
990
987
  ]
991
988
  end
992
989
 
990
+ # Executes the allowed exec command and processes the output
991
+ # @param export [Object] The export configuration object
992
+ # @param inherited_code [Array] The inherited code lines
993
+ # @param code_lines [Array] The code lines to append to
994
+ # @param required [Hash] Required code information
995
+ # @return [String, Symbol] The command output or :ux_exec_prohibited if execution failed
996
+ def process_allowed_exec(export, inherited_code, code_lines, required)
997
+ output = export_exec_with_code(
998
+ export, inherited_code, code_lines, required
999
+ )
1000
+ return :ux_exec_prohibited if output == :invalidated
1001
+
1002
+ output.split("\n")
1003
+ end
1004
+
993
1005
  # parse YAML body defining the UX for a single variable
994
1006
  # set ENV value for the variable and return code lines for the same
995
1007
  def code_from_ux_block_to_set_environment_variables(
@@ -1023,105 +1035,30 @@ module MarkdownExec
1023
1035
  code_lines.push "[[ -z $#{precondition} ]] && exit 1"
1024
1036
  end
1025
1037
 
1026
- exportable = true
1027
1038
  if only_default
1028
- value = case export.default
1029
- # echo > default
1030
- when :echo
1031
- raise unless export.echo.present?
1032
-
1033
- output = export_echo_with_code(
1034
- export, inherited_code, code_lines, required
1035
- )
1036
- if output == :invalidated
1037
- return :ux_exec_prohibited
1038
- end
1039
-
1040
- transform_export_value(output, export)
1041
-
1042
- # exec > default
1043
- when :exec
1044
- raise unless export.exec.present?
1045
-
1046
- output = export_exec_with_code(
1047
- export, inherited_code, code_lines, required
1048
- )
1049
- if output == :invalidated
1050
- return :ux_exec_prohibited
1051
- end
1052
-
1053
- transform_export_value(output, export)
1054
-
1055
- # default
1056
- else
1057
- export.default.to_s
1058
- end
1059
- else
1060
- value = nil
1061
-
1062
- # echo > exec
1063
- if export.echo
1064
- value = export_echo_with_code(
1039
+ value, exportable, transformable =
1040
+ ux_block_export_automatic(
1065
1041
  export, inherited_code, code_lines, required
1066
1042
  )
1067
- if value == :invalidated
1068
- return :ux_exec_prohibited
1069
- end
1070
-
1071
- # exec > allowed
1072
- elsif export.exec
1073
- value = export_exec_with_code(
1074
- export, inherited_code, code_lines, required
1043
+ else
1044
+ value, exportable, transformable =
1045
+ ux_block_export_activated(
1046
+ export, inherited_code, code_lines, required, exit_prompt
1075
1047
  )
1076
- if value == :invalidated
1077
- return :ux_exec_prohibited
1078
- end
1079
-
1080
- # allowed > prompt
1081
- elsif export.allowed && export.allowed.count.positive?
1082
- case (choice = prompt_select_code_filename(
1083
- [exit_prompt] + export.allowed,
1084
- string: export.prompt,
1085
- color_sym: :prompt_color_after_script_execution
1086
- ))
1087
- when exit_prompt
1088
- exportable = false
1089
- else
1090
- value = choice
1091
- end
1092
-
1093
- # prompt > default
1094
- elsif export.prompt.present?
1095
- begin
1096
- loop do
1097
- print "#{export.prompt} [#{export.default}]: "
1098
- value = gets.chomp
1099
- value = export.default.to_s if value.empty?
1100
- caps = NamedCaptureExtractor.extract_named_groups(value,
1101
- export.validate)
1102
- break if caps
1103
-
1104
- # invalid input, retry
1105
- end
1106
- rescue Interrupt
1107
- exportable = false
1108
- end
1048
+ end
1049
+ return :ux_exec_prohibited if value == :ux_exec_prohibited
1109
1050
 
1110
- # default
1111
- else
1112
- value = export.default
1051
+ if SelectResponse.continue?(value)
1052
+ if transformable
1053
+ value = transform_export_value(value, export)
1113
1054
  end
1114
1055
 
1115
1056
  if exportable
1116
- value = transform_export_value(value, export)
1057
+ ENV[export.name] = value.to_s
1058
+ code_lines.push code_line_safe_assign(export.name, value,
1059
+ force: force)
1117
1060
  end
1118
1061
  end
1119
-
1120
- if exportable
1121
- ENV[export.name] = value.to_s
1122
- code_lines.push code_line_safe_assign(export.name, value,
1123
- force: force)
1124
- end
1125
1062
  else
1126
1063
  raise "Invalid data type: #{data.inspect}"
1127
1064
  end
@@ -1425,10 +1362,30 @@ module MarkdownExec
1425
1362
  line_cap[:line] ||= ''
1426
1363
 
1427
1364
  line_caps = [line_cap]
1428
- if wrap && line_cap[:text].length > screen_width_for_wrapping
1429
- wrapper = StringWrapper.new(width: screen_width_for_wrapping - line_cap[:indent].length)
1430
- line_caps = wrapper.wrap(line_cap[:text]).map do |wrapped_line|
1431
- line_cap.dup.merge(text: wrapped_line)
1365
+
1366
+ # split text with newlines, from variable expansion
1367
+ if line_cap[:text].include?("\n")
1368
+ lines = line_cap[:text].split("\n")
1369
+ line_caps = (lines.map do |line|
1370
+ line_cap.dup.merge(text: line)
1371
+ end.to_a)
1372
+ end
1373
+
1374
+ # wrap text on multiple lines to screen width, replacing line_caps
1375
+ if wrap
1376
+ line_caps = line_caps.flat_map do |line_cap|
1377
+ text = line_cap[:text]
1378
+ wrapper = StringWrapper.new(width: screen_width_for_wrapping - line_cap[:indent].length)
1379
+
1380
+ if text.length > screen_width_for_wrapping
1381
+ # Wrap this text and create line_cap objects for each part
1382
+ wrapper.wrap(text).map do |wrapped_text|
1383
+ line_cap.dup.merge(text: wrapped_text)
1384
+ end
1385
+ else
1386
+ # No change needed for this line
1387
+ line_cap
1388
+ end
1432
1389
  end
1433
1390
  end
1434
1391
 
@@ -1527,7 +1484,9 @@ module MarkdownExec
1527
1484
  if block_given?
1528
1485
  # expand references only if block is recognized (not a comment)
1529
1486
  yield if block_given?
1530
- mbody = fcb.body[0].match @delegate_object[criteria[:match]]
1487
+
1488
+ # parse multiline to capture output of variable expansion
1489
+ mbody = fcb.body[0].match Regexp.new(@delegate_object[criteria[:match]], Regexp::MULTILINE)
1531
1490
  end
1532
1491
 
1533
1492
  create_and_add_chrome_block(
@@ -2432,7 +2391,8 @@ module MarkdownExec
2432
2391
  # update blocks
2433
2392
  #
2434
2393
  Regexp.union(replacements.keys.map do |word|
2435
- Regexp.new(Regexp.escape(word))
2394
+ # match multiline text from variable expansion
2395
+ Regexp.new(Regexp.escape(word), Regexp::MULTILINE)
2436
2396
  end).tap do |pattern|
2437
2397
  menu_blocks.each do |block|
2438
2398
  next if exclude_types.include?(block.type)
@@ -2461,7 +2421,7 @@ module MarkdownExec
2461
2421
 
2462
2422
  def expand_variable_references!(
2463
2423
  blocks:,
2464
- echo_format: 'echo $%s',
2424
+ echo_format: 'echo "$%s"',
2465
2425
  group_name: :variable,
2466
2426
  initial_code_required: false,
2467
2427
  key_format: '${%s}',
@@ -2489,9 +2449,11 @@ module MarkdownExec
2489
2449
  expand_blocks_with_replacements(blocks, replacements)
2490
2450
  end
2491
2451
 
2492
- def export_echo_with_code(export, inherited_code, code_lines, required)
2452
+ def export_echo_with_code_single(export_echo, inherited_code, code_lines,
2453
+ required)
2454
+ code = %(printf '%s' "#{export_echo}")
2493
2455
  value = execute_temporary_script(
2494
- %Q{eval printf '%s' "#{export.echo}"},
2456
+ code,
2495
2457
  (inherited_code || []) +
2496
2458
  code_lines + required[:code]
2497
2459
  )
@@ -2501,6 +2463,26 @@ module MarkdownExec
2501
2463
  value
2502
2464
  end
2503
2465
 
2466
+ def export_echo_with_code(export, inherited_code, code_lines, required,
2467
+ force:)
2468
+ exportable = true
2469
+ case export.echo
2470
+ when String
2471
+ value = export_echo_with_code_single(export.echo, inherited_code,
2472
+ code_lines, required)
2473
+ when Hash
2474
+ # each item in the hash is a variable name and value
2475
+ export.echo.each do |name, expression|
2476
+ value = export_echo_with_code_single(expression, inherited_code,
2477
+ code_lines, required)
2478
+ ENV[name] = value.to_s
2479
+ code_lines.push code_line_safe_assign(name, value, force: force)
2480
+ end
2481
+ exportable = false
2482
+ end
2483
+ [value, exportable]
2484
+ end
2485
+
2504
2486
  def export_exec_with_code(export, inherited_code, code_lines, required)
2505
2487
  value = execute_temporary_script(
2506
2488
  export.exec,
@@ -2555,7 +2537,7 @@ module MarkdownExec
2555
2537
  end
2556
2538
 
2557
2539
  @dml_blocks_in_file.find(&match_block) ||
2558
- @dml_menu_blocks.find(&match_block)
2540
+ @dml_menu_blocks.find(&match_block)
2559
2541
  end
2560
2542
 
2561
2543
  # find a block by its original (undecorated) name or nickname (not visible in menu)
@@ -2763,7 +2745,7 @@ module MarkdownExec
2763
2745
  menu_entries, current_display_format
2764
2746
  )
2765
2747
 
2766
- selection = prompt_select_code_filename(
2748
+ selection = prompt_select_from_list(
2767
2749
  menu_options,
2768
2750
  string: menu_title,
2769
2751
  color_sym: :prompt_color_after_script_execution
@@ -2819,7 +2801,7 @@ module MarkdownExec
2819
2801
  def iter_source_blocks(source, source_id: nil, &block)
2820
2802
  case source
2821
2803
  when 1
2822
- blocks_from_nested_files(source_id: source_id).each(&block)
2804
+ blocks_from_nested_files(source_id: source_id).blocks.each(&block)
2823
2805
  when 2
2824
2806
  @dml_blocks_in_file.each(&block)
2825
2807
  when 3
@@ -2862,7 +2844,8 @@ module MarkdownExec
2862
2844
  nil),
2863
2845
  end_pattern: @delegate_object.fetch(:output_assignment_end, nil),
2864
2846
  scan1: @delegate_object.fetch(:output_assignment_match, nil),
2865
- format1: @delegate_object.fetch(:output_assignment_format, nil)
2847
+ format1: @delegate_object.fetch(:output_assignment_format, nil),
2848
+ name: ''
2866
2849
  )
2867
2850
 
2868
2851
  else
@@ -3123,7 +3106,7 @@ module MarkdownExec
3123
3106
  else
3124
3107
  ## user selects from existing files or other
3125
3108
  #
3126
- case (name = prompt_select_code_filename(
3109
+ case (name = prompt_select_from_list(
3127
3110
  [@delegate_object[:prompt_filespec_back]] + files,
3128
3111
  string: @delegate_object[:prompt_select_code_file],
3129
3112
  color_sym: :prompt_color_after_script_execution
@@ -3154,11 +3137,19 @@ module MarkdownExec
3154
3137
  end
3155
3138
 
3156
3139
  def mdoc_and_blocks_from_nested_files(source_id: nil)
3157
- menu_blocks = blocks_from_nested_files(source_id: source_id)
3158
- mdoc = MDoc.new(menu_blocks) do |nopts|
3140
+ blocks_results = blocks_from_nested_files(source_id: source_id)
3141
+
3142
+ blocks_results.results.select do |_id, result|
3143
+ result.failure?
3144
+ end.each do |id, result|
3145
+ HashDelegator.error_handler("#{id} - #{result.to_yaml}")
3146
+ end
3147
+
3148
+ mdoc = MDoc.new(blocks_results.blocks) do |nopts|
3159
3149
  @delegate_object.merge!(nopts)
3160
3150
  end
3161
- [menu_blocks, mdoc]
3151
+
3152
+ [blocks_results.blocks, mdoc]
3162
3153
  end
3163
3154
 
3164
3155
  ## Handles the file loading and returns the blocks
@@ -3305,6 +3296,19 @@ module MarkdownExec
3305
3296
  end
3306
3297
  end
3307
3298
 
3299
+ def menu_from_list_with_back(list)
3300
+ case (name = prompt_select_from_list(
3301
+ [@delegate_object[:prompt_filespec_back]] + list,
3302
+ string: @delegate_object[:prompt_select_code_file],
3303
+ color_sym: :prompt_color_after_script_execution
3304
+ ))
3305
+ when @delegate_object[:prompt_filespec_back]
3306
+ SelectResponse::BACK
3307
+ else
3308
+ name
3309
+ end
3310
+ end
3311
+
3308
3312
  def menu_toggle_collapsible_block(selected)
3309
3313
  # return true if @compress_ids.key?(fcb.id) && !!@compress_ids[fcb.id]
3310
3314
  # return false if @expand_ids.key?(fcb.id) && !!@expand_ids[fcb.id]
@@ -3538,7 +3542,7 @@ module MarkdownExec
3538
3542
  # private
3539
3543
 
3540
3544
  def process_string_array(arr, begin_pattern: nil, end_pattern: nil, scan1: nil,
3541
- format1: nil)
3545
+ format1: nil, name: '')
3542
3546
  in_block = !begin_pattern.present?
3543
3547
  collected_lines = []
3544
3548
 
@@ -3560,6 +3564,9 @@ module MarkdownExec
3560
3564
  collected_lines << formatted
3561
3565
  end
3562
3566
  end
3567
+ elsif format1.present?
3568
+ formatted = format(format1, { value: line })
3569
+ collected_lines << formatted
3563
3570
  else
3564
3571
  collected_lines << line
3565
3572
  end
@@ -3685,9 +3692,22 @@ module MarkdownExec
3685
3692
  0
3686
3693
  end
3687
3694
 
3695
+ def prompt_select_continue(filter: true, quiet: true)
3696
+ sel = @prompt.select(
3697
+ string_send_color(@delegate_object[:prompt_after_script_execution],
3698
+ :prompt_color_after_script_execution),
3699
+ filter: filter,
3700
+ quiet: quiet
3701
+ ) do |menu|
3702
+ menu.choice @delegate_object[:prompt_yes]
3703
+ menu.choice @delegate_object[:prompt_exit]
3704
+ end
3705
+ sel == @delegate_object[:prompt_exit] ? MenuState::EXIT : MenuState::CONTINUE
3706
+ end
3707
+
3688
3708
  # public
3689
3709
 
3690
- def prompt_select_code_filename(
3710
+ def prompt_select_from_list(
3691
3711
  filenames,
3692
3712
  color_sym: :prompt_color_after_script_execution,
3693
3713
  cycle: true,
@@ -3713,19 +3733,6 @@ module MarkdownExec
3713
3733
  end
3714
3734
  end
3715
3735
 
3716
- def prompt_select_continue(filter: true, quiet: true)
3717
- sel = @prompt.select(
3718
- string_send_color(@delegate_object[:prompt_after_script_execution],
3719
- :prompt_color_after_script_execution),
3720
- filter: filter,
3721
- quiet: quiet
3722
- ) do |menu|
3723
- menu.choice @delegate_object[:prompt_yes]
3724
- menu.choice @delegate_object[:prompt_exit]
3725
- end
3726
- sel == @delegate_object[:prompt_exit] ? MenuState::EXIT : MenuState::CONTINUE
3727
- end
3728
-
3729
3736
  # user prompt to exit if the menu will be displayed again
3730
3737
  #
3731
3738
  def prompt_user_exit(block_name_from_cli:, selected:)
@@ -3799,27 +3806,6 @@ module MarkdownExec
3799
3806
  end&.compact
3800
3807
  end
3801
3808
 
3802
- def saved_asset_for_history(
3803
- file:, form:, match_info:
3804
- )
3805
- OpenStruct.new(
3806
- file: file[(Dir.pwd.length + 1)..-1],
3807
- full: file,
3808
- row: format(
3809
- form,
3810
- # default '*' so unknown parameters are given a wildcard
3811
- match_info.names.each_with_object(Hash.new('*')) do |name, hash|
3812
- hash[name.to_sym] = match_info[name]
3813
- end
3814
- )
3815
- )
3816
- rescue KeyError
3817
- # pp $!, $@
3818
- warn "Cannot format with: #{@delegate_object[:saved_history_format]}"
3819
- error_handler('saved_history_format')
3820
- :break
3821
- end
3822
-
3823
3809
  # Processes YAML data from the selected menu item, updating delegate
3824
3810
  # objects and optionally printing formatted output.
3825
3811
  # @param selected [Hash] Selected item from the menu containing a YAML body.
@@ -3874,8 +3860,8 @@ module MarkdownExec
3874
3860
  # console [height, width]. If not provided or if the terminal
3875
3861
  # is resized, it will be set to the current console dimensions.
3876
3862
  # - :select_page_height [Integer, nil] The height of the page for
3877
- # selection. If not provided or if not positive, it will be set
3878
- # to the maximum of (console height - 3) or 4.
3863
+ # selection. If not provided or if not positive, it
3864
+ # will be set to the maximum of (console height - 3) or 4.
3879
3865
  # - :per_page [Integer, nil] The number of items per page. If
3880
3866
  # :select_page_height is not provided or if not positive, it
3881
3867
  # will be set to the maximum of (console height - 3) or 4.
@@ -4002,7 +3988,7 @@ module MarkdownExec
4002
3988
  ## user selects from existing files or other
4003
3989
  # input into path with wildcard for easy entry
4004
3990
  #
4005
- case (name = prompt_select_code_filename(
3991
+ case (name = prompt_select_from_list(
4006
3992
  [@delegate_object[:prompt_filespec_back],
4007
3993
  @delegate_object[:prompt_filespec_other]] + files,
4008
3994
  string: @delegate_object[:prompt_select_code_file],
@@ -4039,6 +4025,27 @@ module MarkdownExec
4039
4025
  ).generate_name
4040
4026
  end
4041
4027
 
4028
+ def saved_asset_for_history(
4029
+ file:, form:, match_info:
4030
+ )
4031
+ OpenStruct.new(
4032
+ file: file[(Dir.pwd.length + 1)..-1],
4033
+ full: file,
4034
+ row: format(
4035
+ form,
4036
+ # default '*' so unknown parameters are given a wildcard
4037
+ match_info.names.each_with_object(Hash.new('*')) do |name, hash|
4038
+ hash[name.to_sym] = match_info[name]
4039
+ end
4040
+ )
4041
+ )
4042
+ rescue KeyError
4043
+ # pp $!, $@
4044
+ warn "Cannot format with: #{@delegate_object[:saved_history_format]}"
4045
+ error_handler('saved_history_format')
4046
+ :break
4047
+ end
4048
+
4042
4049
  def screen_width
4043
4050
  width = @delegate_object[:screen_width]
4044
4051
  if width&.positive?
@@ -4387,6 +4394,147 @@ module MarkdownExec
4387
4394
  @delegate_object.merge!(options)
4388
4395
  end
4389
4396
 
4397
+ def ux_block_export_activated(export, inherited_code, code_lines, required,
4398
+ exit_prompt)
4399
+ exportable = true
4400
+ transformable = true
4401
+ [if export.allowed.present?
4402
+ if export.allowed == :echo
4403
+ output, exportable = export_echo_with_code(
4404
+ export, inherited_code, code_lines, required, force: true
4405
+ )
4406
+ return :ux_exec_prohibited if output == :invalidated
4407
+
4408
+ menu_from_list_with_back(output.split("\n"))
4409
+
4410
+ elsif export.allowed == :exec
4411
+ output = process_allowed_exec(export, inherited_code,
4412
+ code_lines, required)
4413
+ return :ux_exec_prohibited if output == :ux_exec_prohibited
4414
+
4415
+ menu_from_list_with_back(output)
4416
+
4417
+ else
4418
+ menu_from_list_with_back(export.allowed)
4419
+ end
4420
+
4421
+ # echo > exec
4422
+ elsif export.echo
4423
+ output, exportable = export_echo_with_code(
4424
+ export, inherited_code, code_lines, required, force: true
4425
+ )
4426
+ return :ux_exec_prohibited if output == :invalidated
4427
+
4428
+ output
4429
+
4430
+ # exec > allowed
4431
+ elsif export.exec
4432
+ output = export_exec_with_code(
4433
+ export, inherited_code, code_lines, required
4434
+ )
4435
+ return :ux_exec_prohibited if output == :invalidated
4436
+
4437
+ output
4438
+
4439
+ # allowed > prompt
4440
+ elsif export.allowed && export.allowed.count.positive?
4441
+ case (choice = prompt_select_from_list(
4442
+ [exit_prompt] + export.allowed,
4443
+ string: export.prompt,
4444
+ color_sym: :prompt_color_after_script_execution
4445
+ ))
4446
+ when exit_prompt
4447
+ exportable = false
4448
+ transformable = false
4449
+ nil
4450
+ else
4451
+ choice
4452
+ end
4453
+
4454
+ # prompt > default
4455
+ elsif export.prompt.present?
4456
+ begin
4457
+ loop do
4458
+ print "#{export.prompt} [#{export.default}]: "
4459
+ output = gets.chomp
4460
+ output = export.default.to_s if output.empty?
4461
+ caps = NamedCaptureExtractor.extract_named_groups(output,
4462
+ export.validate)
4463
+ break if caps
4464
+
4465
+ # invalid input, retry
4466
+ end
4467
+ rescue Interrupt
4468
+ exportable = false
4469
+ transformable = false
4470
+ end
4471
+
4472
+ output
4473
+
4474
+ # default
4475
+ else
4476
+ transformable = false
4477
+ export.default
4478
+ end, exportable, transformable]
4479
+ end
4480
+
4481
+ def ux_block_export_automatic(export, inherited_code, code_lines, required)
4482
+ transformable = true
4483
+ exportable = true
4484
+ [case export.default
4485
+ when :allowed
4486
+ raise unless export.allowed.present?
4487
+
4488
+ if export.allowed == :echo
4489
+ output, exportable = export_echo_with_code(
4490
+ export, inherited_code, code_lines, required, force: false
4491
+ )
4492
+ return :ux_exec_prohibited if output == :invalidated
4493
+
4494
+ exportable && output.split("\n").first
4495
+
4496
+ elsif export.allowed == :exec
4497
+ output = process_allowed_exec(export, inherited_code,
4498
+ code_lines, required)
4499
+ return :ux_exec_prohibited if output == :ux_exec_prohibited
4500
+
4501
+ output.first
4502
+
4503
+ else
4504
+ export.allowed.first
4505
+ end
4506
+
4507
+ # echo > default
4508
+ when :echo
4509
+ raise unless export.echo.present?
4510
+
4511
+ output, exportable = export_echo_with_code(
4512
+ export, inherited_code, code_lines, required, force: false
4513
+ )
4514
+ return :ux_exec_prohibited if output == :invalidated
4515
+
4516
+ output
4517
+
4518
+ # exec > default
4519
+ when :exec
4520
+ raise unless export.exec.present?
4521
+
4522
+ output = export_exec_with_code(
4523
+ export, inherited_code, code_lines, required
4524
+ )
4525
+ return :ux_exec_prohibited if output == :invalidated
4526
+
4527
+ output
4528
+
4529
+ # default
4530
+ else
4531
+ transformable = false
4532
+ export.default.to_s
4533
+ end,
4534
+ exportable,
4535
+ transformable]
4536
+ end
4537
+
4390
4538
  def vux_await_user_selection(prior_answer: @dml_block_selection)
4391
4539
  @dml_block_state = load_cli_or_user_selected_block(
4392
4540
  all_blocks: @dml_blocks_in_file,
@@ -5324,7 +5472,7 @@ module MarkdownExec
5324
5472
  end
5325
5473
 
5326
5474
  def test_blocks_from_nested_files
5327
- result = @hd.blocks_from_nested_files
5475
+ result = @hd.blocks_from_nested_files.blocks
5328
5476
  assert_kind_of Array, result
5329
5477
  assert_kind_of FCB, result.first
5330
5478
  end
@@ -5333,7 +5481,7 @@ module MarkdownExec
5333
5481
  @hd = HashDelegator.new(no_chrome: true)
5334
5482
  @hd.expects(:create_and_add_chrome_blocks).never
5335
5483
 
5336
- result = @hd.blocks_from_nested_files
5484
+ result = @hd.blocks_from_nested_files.blocks
5337
5485
 
5338
5486
  assert_kind_of Array, result
5339
5487
  end