markdown_exec 2.8.4 → 3.0.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -1
  3. data/Gemfile.lock +1 -1
  4. data/Rakefile +0 -33
  5. data/bats/bats.bats +2 -0
  6. data/bats/block-type-link.bats +1 -1
  7. data/bats/block-type-ux-allowed.bats +2 -2
  8. data/bats/block-type-ux-default.bats +8 -0
  9. data/bats/block-type-ux-invalid.bats +1 -1
  10. data/bats/{block-type-ux-preconditions.bats → block-type-ux-required-variables.bats} +1 -1
  11. data/bats/block-type-ux-row-format.bats +1 -1
  12. data/bats/block-type-ux-sources.bats +36 -0
  13. data/bats/border.bats +1 -1
  14. data/bats/cli.bats +2 -2
  15. data/bats/command-substitution-options.bats +14 -0
  16. data/bats/command-substitution.bats +1 -1
  17. data/bats/fail.bats +5 -2
  18. data/bats/indented-block-type-vars.bats +1 -1
  19. data/bats/markup.bats +1 -1
  20. data/bats/option-expansion.bats +8 -0
  21. data/bats/table-column-truncate.bats +1 -1
  22. data/bats/test_helper.bash +50 -5
  23. data/docs/dev/bats-document-configuration.md +1 -1
  24. data/docs/dev/block-type-ux-allowed.md +5 -5
  25. data/docs/dev/block-type-ux-auto.md +9 -5
  26. data/docs/dev/block-type-ux-chained.md +4 -2
  27. data/docs/dev/block-type-ux-default.md +42 -0
  28. data/docs/dev/block-type-ux-echo-hash.md +6 -1
  29. data/docs/dev/block-type-ux-echo.md +3 -1
  30. data/docs/dev/block-type-ux-exec.md +3 -4
  31. data/docs/dev/block-type-ux-hidden.md +3 -0
  32. data/docs/dev/block-type-ux-require.md +9 -18
  33. data/docs/dev/{block-type-ux-preconditions.md → block-type-ux-required-variables.md} +1 -2
  34. data/docs/dev/block-type-ux-row-format.md +3 -4
  35. data/docs/dev/block-type-ux-sources.md +57 -0
  36. data/docs/dev/block-type-ux-transform.md +0 -4
  37. data/docs/dev/command-substitution-options.md +61 -0
  38. data/docs/dev/indented-block-type-vars.md +1 -0
  39. data/docs/dev/menu-pagination-indent.md +123 -0
  40. data/docs/dev/menu-pagination.md +111 -0
  41. data/docs/dev/option-expansion.md +10 -0
  42. data/lib/ansi_formatter.rb +2 -0
  43. data/lib/block_cache.rb +197 -0
  44. data/lib/command_result.rb +57 -0
  45. data/lib/constants.rb +18 -0
  46. data/lib/error_reporting.rb +38 -0
  47. data/lib/evaluate_shell_expressions.rb +43 -18
  48. data/lib/fcb.rb +114 -11
  49. data/lib/hash_delegator.rb +595 -359
  50. data/lib/markdown_exec/version.rb +1 -1
  51. data/lib/markdown_exec.rb +136 -45
  52. data/lib/mdoc.rb +74 -23
  53. data/lib/menu.src.yml +27 -9
  54. data/lib/menu.yml +23 -8
  55. data/lib/namer.rb +1 -3
  56. data/lib/value_or_exception.rb +76 -0
  57. metadata +18 -4
@@ -7,5 +7,5 @@ module MarkdownExec
7
7
  BIN_NAME = 'mde'
8
8
  GEM_NAME = 'markdown_exec'
9
9
  TAP_DEBUG = 'MDE_DEBUG'
10
- VERSION = '2.8.4'
10
+ VERSION = '3.0.0'
11
11
  end
data/lib/markdown_exec.rb CHANGED
@@ -21,6 +21,7 @@ require_relative 'cli'
21
21
  require_relative 'color_scheme'
22
22
  require_relative 'directory_searcher'
23
23
  require_relative 'env'
24
+ require_relative 'error_reporting'
24
25
  require_relative 'exceptions'
25
26
  require_relative 'fcb'
26
27
  require_relative 'filter'
@@ -110,11 +111,16 @@ module MarkdownExec
110
111
  # Prepends the age of the file in days to the file name for display in a menu.
111
112
  # @param filename [String] the name of the file
112
113
  # @return [String] modified file name with age prepended
113
- def self.for_menu(filename)
114
+ def self.for_menu(filename, colorize: true, histogram: true)
114
115
  file_age = (Time.now - File.mtime(filename)) / (60 * 60 * 24 * 30)
115
- filename = ColorScheme.colorize_path(filename)
116
+ filename = ColorScheme.colorize_path(filename) if colorize
116
117
 
117
- " #{Histogram.display(file_age, 0, 11, 12, inverse: false)}: #{filename}"
118
+ if histogram
119
+ " #{Histogram.display(file_age, 0, 11, 12,
120
+ inverse: false)}: #{filename}"
121
+ else
122
+ filename
123
+ end
118
124
  end
119
125
 
120
126
  # Removes the age from the string to retrieve the original file name.
@@ -165,33 +171,30 @@ module MarkdownExec
165
171
  end
166
172
 
167
173
  def build_menu(file_names, directory_names, found_in_block_names,
168
- file_name_choices, choices_from_block_names)
174
+ file_name_choices, choices_from_block_names,
175
+ colorize: true)
169
176
  choices = []
170
177
 
171
178
  # Adding section title and data for file names
172
- choices << {
173
- disabled: '',
174
- name: AnsiString.new("in #{file_names[:section_title]}")
175
- .send(@chrome_color)
176
- }
177
- choices += file_names[:data].map { |str| FileInMenu.for_menu(str) }
179
+ text = "in #{file_names[:section_title]}"
180
+ text = AnsiString.new(text).send(@chrome_color) if colorize
181
+ choices << { disabled: '', name: text }
182
+ choices += file_names[:data].map do |str|
183
+ FileInMenu.for_menu(str, colorize: colorize)
184
+ end
178
185
 
179
186
  # Conditionally add directory names if data is present
180
187
  if directory_names[:data].any?
181
- choices << {
182
- disabled: '',
183
- name: AnsiString.new("in #{directory_names[:section_title]}")
184
- .send(@chrome_color)
185
- }
188
+ text = "in #{directory_names[:section_title]}"
189
+ text = AnsiString.new(text).send(@chrome_color) if colorize
190
+ choices << { disabled: '', name: text }
186
191
  choices += file_name_choices
187
192
  end
188
193
 
189
194
  # Adding found in block names
190
- choices << {
191
- disabled: '',
192
- name: AnsiString.new("in #{found_in_block_names[:section_title]}")
193
- .send(@chrome_color)
194
- }
195
+ text = "in #{found_in_block_names[:section_title]}"
196
+ text = AnsiString.new(text).send(@chrome_color) if colorize
197
+ choices << { disabled: '', name: text }
195
198
  choices += choices_from_block_names
196
199
 
197
200
  choices
@@ -199,6 +202,8 @@ module MarkdownExec
199
202
  end
200
203
 
201
204
  class SearchResultsReport < DirectorySearcher
205
+ include ErrorReporting
206
+
202
207
  def directory_names(search_options, highlight_value)
203
208
  matched_directories = find_directory_names
204
209
  {
@@ -238,6 +243,8 @@ module MarkdownExec
238
243
  end,
239
244
  matched_contents: matched_contents
240
245
  }
246
+ rescue StandardError => err
247
+ report_and_reraise(err)
241
248
  end
242
249
 
243
250
  def file_names(search_options, highlight_value)
@@ -346,6 +353,8 @@ module MarkdownExec
346
353
  }
347
354
  end
348
355
 
356
+ public
357
+
349
358
  def choices_from_block_names(value, found_in_block_names)
350
359
  found_in_block_names[:matched_contents].map do |matched_contents|
351
360
  filename, details, = matched_contents
@@ -360,14 +369,16 @@ module MarkdownExec
360
369
  end.flatten
361
370
  end
362
371
 
363
- def choices_from_file_names(directory_names)
372
+ def choices_from_file_names(directory_names, colorize: true,
373
+ histogram: true, pattern: '*')
364
374
  directory_names[:data].map do |dn|
365
- find_files('*', [dn], exclude_dirs: true)
366
- end.flatten(1).map { |str| FileInMenu.for_menu(str) }
375
+ find_files(pattern, [dn], exclude_dirs: true)
376
+ end.flatten(1).map do |str|
377
+ FileInMenu.for_menu(str, colorize: colorize,
378
+ histogram: histogram)
379
+ end
367
380
  end
368
381
 
369
- public
370
-
371
382
  ## Determines the correct filename to use for searching files
372
383
  #
373
384
  def determine_filename(
@@ -658,6 +669,32 @@ module MarkdownExec
658
669
  @options.merge(@options.run_state.to_h)
659
670
  end
660
671
 
672
+ def iter_source_blocks(source, &block)
673
+ case source
674
+ when 1
675
+ HashDelegator.new(@options).blocks_from_nested_files.each(&block)
676
+ when 2
677
+ blocks_in_file, =
678
+ HashDelegator.new(@options)
679
+ .mdoc_menu_and_blocks_from_nested_files(LinkState.new)
680
+ blocks_in_file.each(&block)
681
+ when 3
682
+ _, menu_blocks, =
683
+ HashDelegator.new(@options)
684
+ .mdoc_menu_and_blocks_from_nested_files(LinkState.new)
685
+ menu_blocks.each(&block)
686
+ else
687
+ @options.iter_blocks_from_nested_files do |btype, fcb|
688
+ case btype
689
+ when :blocks
690
+ yield fcb
691
+ when :filter
692
+ %i[blocks]
693
+ end
694
+ end
695
+ end
696
+ end
697
+
661
698
  ##
662
699
  # Returns a lambda expression based on the given procname.
663
700
  # @param procname [String] The name of the process to generate a lambda for.
@@ -782,7 +819,7 @@ module MarkdownExec
782
819
  # Reports and executes block logic
783
820
  def mde_vux_main_loop(files)
784
821
  @options[:filename] = select_document_if_multiple(files)
785
- @options.vux_main_loop do |type, data|
822
+ @options.vux_main_loop(menu_from_yaml: @menu_from_yaml) do |type, data|
786
823
  case type
787
824
  when :command_names
788
825
  simple_commands(data).keys
@@ -802,12 +839,13 @@ module MarkdownExec
802
839
  # Generates a menu suitable for OptionParser from the menu items defined in YAML format.
803
840
  # @return [Array<Hash>] The array of option hashes for OptionParser.
804
841
  def menu_for_optparse
805
- menu_from_yaml.map do |menu_item|
806
- menu_item.merge(
807
- opt_name: menu_item[:opt_name]&.to_sym,
808
- proccode: lambda_for_procname(menu_item[:procname], options)
809
- )
810
- end
842
+ @menu_from_yaml =
843
+ menu_from_yaml.map do |menu_item|
844
+ menu_item.merge(
845
+ opt_name: menu_item[:opt_name]&.to_sym,
846
+ proccode: lambda_for_procname(menu_item[:procname], options)
847
+ )
848
+ end
811
849
  end
812
850
 
813
851
  def menu_help
@@ -963,6 +1001,8 @@ module MarkdownExec
963
1001
  @options[:saved_filename_replacement])
964
1002
  end
965
1003
 
1004
+ public
1005
+
966
1006
  def select_document_if_multiple(files = list_markdown_files_in_path,
967
1007
  cycle: true,
968
1008
  prompt: options[:prompt_select_md].to_s)
@@ -984,6 +1024,8 @@ module MarkdownExec
984
1024
  )
985
1025
  end
986
1026
 
1027
+ private
1028
+
987
1029
  # Presents a TTY prompt to select an option or exit, returns selected option or nil
988
1030
  def select_option_or_exit(prompt_text, strings, opts = {})
989
1031
  @options.select_option_with_metadata(
@@ -1049,21 +1091,70 @@ module MarkdownExec
1049
1091
  end # class MarkParse
1050
1092
  end # module MarkdownExec
1051
1093
 
1052
- if $PROGRAM_NAME == __FILE__
1053
- require 'bundler/setup'
1054
- Bundler.require(:default)
1094
+ return if $PROGRAM_NAME != __FILE__
1055
1095
 
1056
- require 'minitest/autorun'
1096
+ require 'bundler/setup'
1097
+ Bundler.require(:default)
1057
1098
 
1058
- module MarkdownExec
1059
- def test_select_block
1060
- blocks = [block1, block2]
1061
- menu = [m1, m2]
1099
+ require 'minitest/autorun'
1062
1100
 
1063
- block, state = obj.select_block(blocks, menu, nil, {})
1101
+ module MarkdownExec
1102
+ class TestMarkdownTableFormatter < Minitest::Test
1103
+ def test_select_document_if_multiple
1104
+ find_path = "#{`pwd`.chomp}/fixtures"
1105
+ value = 'document'
1106
+ searcher = SearchResultsReport.new(value, [find_path])
1107
+ options = {
1108
+ ansi_formatter_color: false,
1109
+ block_name_match: ':(?<title>\\S+)( |$)',
1110
+ block_name_nick_match: '^\[.*\]$',
1111
+ fenced_start_and_end_regex: '^(?<indent>[ \t]*)`{3,}',
1112
+ menu_active_color_pastel_messages: %w[inverse]
1113
+ }
1114
+
1115
+ file_names = searcher.file_names(options, value)
1116
+ assert_equal ({ section_title: 'file names', data: [], formatted_text: [{ content: '' }] }),
1117
+ file_names
1064
1118
 
1065
- assert_equal block1, block
1066
- assert_equal MenuState::CONTINUE, state
1119
+ found_in_block_names = searcher.found_in_block_names(options, value,
1120
+ formspec: '%<line>s')
1121
+ assert_equal ({ section_title: 'block names',
1122
+ data: [],
1123
+ formatted_text: [],
1124
+ matched_contents: [] }), found_in_block_names
1125
+
1126
+ directory_names = searcher.directory_names(options, value)
1127
+ assert_equal ({ section_title: 'directory names',
1128
+ data: ["#{Dir.getwd}/fixtures"],
1129
+ formatted_text: [{ content: ["#{Dir.getwd}/fixtures"] }] }), directory_names
1130
+
1131
+ mp = MarkdownExec::MarkParse.new(options)
1132
+
1133
+ file_name_choices = mp.choices_from_file_names(
1134
+ directory_names, colorize: false, histogram: false, pattern: 'y*'
1135
+ )
1136
+ assert_equal ['fixtures/yaml1.md',
1137
+ 'fixtures/yaml2.md'], file_name_choices
1138
+
1139
+ choices = MenuBuilder.new.build_menu(
1140
+ file_names, directory_names, found_in_block_names,
1141
+ file_name_choices, mp.choices_from_block_names(value, found_in_block_names),
1142
+ colorize: false
1143
+ )
1144
+ assert_equal [{ disabled: '', name: 'in file names' },
1145
+ { disabled: '', name: 'in directory names' },
1146
+ 'fixtures/yaml1.md',
1147
+ 'fixtures/yaml2.md',
1148
+ { disabled: '', name: 'in block names' }], choices
1149
+
1150
+ # @options[:filename] = FileInMenu.from_menu(
1151
+ # mp.select_document_if_multiple(
1152
+ # choices,
1153
+ # # test_select: 0,
1154
+ # prompt: options[:prompt_select_md].to_s +
1155
+ # AnsiString.new(' ¤ Age in months').fg_rgbh_AF_AF_00
1156
+ # )
1157
+ # )
1067
1158
  end
1068
- end # module MarkdownExec
1069
- end # if
1159
+ end # if
1160
+ end # module MarkdownExec
data/lib/mdoc.rb CHANGED
@@ -10,6 +10,45 @@ require_relative 'filter'
10
10
  $pd = false unless defined?($pd)
11
11
 
12
12
  module MarkdownExec
13
+ class MenuFilter
14
+ def initialize(opts)
15
+ @opts = opts.merge(block_name_hidden_match: nil)
16
+ end
17
+
18
+ def fcb_in_menu?(fcb)
19
+ in_menu = Filter.fcb_select?(@opts, fcb)
20
+ unless @opts[:menu_include_imported_blocks]
21
+ in_menu = fcb.fetch(:depth, 0).zero?
22
+ end
23
+ if in_menu && @opts[:hide_blocks_by_name]
24
+ in_menu = !hide_menu_block_on_name(fcb)
25
+ end
26
+ in_menu
27
+ end
28
+
29
+ # Checks if a code block should be hidden based on the given options.
30
+ #
31
+ # @param opts [Hash] The options used for hiding code blocks.
32
+ # @param block [Hash] The code block to check for hiding.
33
+ # @return [Boolean] True if the code block should be hidden; false otherwise.
34
+ #
35
+ # :reek:UtilityFunction
36
+ def hide_menu_block_on_name(block)
37
+ if block.fetch(:chrome, false)
38
+ false
39
+ else
40
+ @opts[:hide_blocks_by_name] &&
41
+ ((@opts[:block_name_hidden_match]&.present? &&
42
+ block.s2title&.match(Regexp.new(@opts[:block_name_hidden_match]))) ||
43
+ (@opts[:block_name_include_match]&.present? &&
44
+ block.s2title&.match(Regexp.new(@opts[:block_name_include_match]))) ||
45
+ (@opts[:block_name_wrapper_match]&.present? &&
46
+ block.s2title&.match(Regexp.new(@opts[:block_name_wrapper_match])))) &&
47
+ (block.s2title&.present? || block[:label]&.present?)
48
+ end
49
+ end
50
+ end
51
+
13
52
  ##
14
53
  # MDoc represents an imported markdown document.
15
54
  #
@@ -26,7 +65,6 @@ module MarkdownExec
26
65
  #
27
66
  def initialize(table = [])
28
67
  @table = table
29
- # !!t @table.count
30
68
  end
31
69
 
32
70
  def collect_block_code_cann(fcb)
@@ -83,7 +121,7 @@ module MarkdownExec
83
121
 
84
122
  nickname = name_block.pub_name
85
123
 
86
- dependencies = collect_dependencies(nickname)
124
+ dependencies = collect_dependencies(source: nickname)
87
125
  # !!t dependencies.count
88
126
  all_dependency_names =
89
127
  collect_unique_names(dependencies).push(nickname).uniq
@@ -91,7 +129,7 @@ module MarkdownExec
91
129
 
92
130
  # select blocks in order of appearance in source documents
93
131
  #
94
- blocks = @table.select do |fcb|
132
+ blocks = table_not_split.select do |fcb|
95
133
  fcb.is_dependency_of?(all_dependency_names)
96
134
  end
97
135
  # !!t blocks.count
@@ -177,15 +215,19 @@ module MarkdownExec
177
215
  # @return [Array<Hash>] An array of code blocks required by the specified code blocks.
178
216
  #
179
217
  def collect_wrapped_blocks(blocks)
180
- blocks.map do |block|
181
- (block[:wraps] || []).map do |wrap|
218
+ blocks.map do |fcb|
219
+ next if fcb.is_split_rest?
220
+
221
+ (fcb[:wraps] || []).map do |wrap|
182
222
  wrap_before = wrap.sub('}', '-before}') ### hardcoded wrap name
183
- @table.select { |fcb| fcb.code_name_included?(wrap_before, wrap) }
223
+ table_not_split.select { |fcb|
224
+ fcb.code_name_included?(wrap_before, wrap)
225
+ }
184
226
  end.flatten(1) +
185
- [block] +
186
- (block[:wraps] || []).reverse.map do |wrap|
227
+ [fcb] +
228
+ (fcb[:wraps] || []).reverse.map do |wrap|
187
229
  wrap_after = wrap.sub('}', '-after}') ### hardcoded wrap name
188
- @table.select { |fcb| fcb.code_name_included?(wrap_after) }
230
+ table_not_split.select { |fcb| fcb.code_name_included?(wrap_after) }
189
231
  end.flatten(1)
190
232
  end.flatten(1).compact
191
233
  end
@@ -325,7 +367,7 @@ module MarkdownExec
325
367
  # @return [Hash] The code block as a hash or the default value if not found.
326
368
  #
327
369
  def get_block_by_anyname(name, default = {})
328
- @table.select do |fcb|
370
+ table_not_split.select do |fcb|
329
371
  fcb.is_named?(name)
330
372
  end.fetch(0, default)
331
373
  end
@@ -405,21 +447,23 @@ module MarkdownExec
405
447
  # @param source [String] The name of the initial source block.
406
448
  # @param memo [Hash] A memoization hash to store resolved dependencies.
407
449
  # @return [Hash] A hash mapping sources to their respective dependencies.
408
- def collect_dependencies(source, memo = {})
409
- return memo unless source
450
+ def collect_dependencies(block: nil, memo: {}, source: nil)
451
+ if block.nil?
452
+ return memo unless source
410
453
 
411
- block = get_block_by_anyname(source)
412
- if block.nil? || block.instance_of?(Hash)
413
- raise "Named code block `#{source}` not found. (@#{__LINE__})"
454
+ block = get_block_by_anyname(source)
455
+ if block.nil? || block.instance_of?(Hash)
456
+ raise "Named code block `#{source}` not found. (@#{__LINE__})"
457
+ end
414
458
  end
415
-
416
459
  return memo unless block.reqs
417
460
 
418
- memo[source] = block.reqs
461
+ memo[block.id] = block.reqs
419
462
 
420
463
  block.reqs.each do |req|
421
- collect_dependencies(req, memo) unless memo.key?(req)
464
+ collect_dependencies(source: req, memo: memo) unless memo.key?(req)
422
465
  end
466
+
423
467
  memo
424
468
  end
425
469
 
@@ -446,6 +490,12 @@ module MarkdownExec
446
490
 
447
491
  selected_elements
448
492
  end
493
+
494
+ # exclude blocks with duplicate code
495
+ # the first block in each split contains the same data as the rest of the split
496
+ def table_not_split
497
+ @table.reject(&:is_split_rest?)
498
+ end
449
499
  end
450
500
  end
451
501
 
@@ -463,24 +513,25 @@ if $PROGRAM_NAME == __FILE__
463
513
  end
464
514
 
465
515
  def test_collect_dependencies_with_no_source
466
- assert_empty @mdoc.collect_dependencies(nil)
516
+ assert_empty @mdoc.collect_dependencies
467
517
  end
468
518
 
469
519
  ### must raise error
470
520
  def test_collect_dependencies_with_nonexistent_source
471
521
  assert_raises(RuntimeError) do
472
- @mdoc.collect_dependencies('nonexistent')
522
+ @mdoc.collect_dependencies(source: 'nonexistent')
473
523
  end
474
524
  end if false
475
525
 
476
526
  def test_collect_dependencies_with_valid_source
477
527
  @mdoc.stubs(:get_block_by_anyname)
478
- .with('source1').returns(OpenStruct.new(reqs: ['source2']))
528
+ .with('source1').returns(OpenStruct.new(id: 'source1',
529
+ reqs: ['source2']))
479
530
  @mdoc.stubs(:get_block_by_anyname)
480
- .with('source2').returns(OpenStruct.new(reqs: []))
531
+ .with('source2').returns(OpenStruct.new(id: 'source2', reqs: []))
481
532
 
482
533
  expected = { 'source1' => ['source2'], 'source2' => [] }
483
- assert_equal expected, @mdoc.collect_dependencies('source1')
534
+ assert_equal expected, @mdoc.collect_dependencies(source: 'source1')
484
535
  end
485
536
  end
486
537
 
data/lib/menu.src.yml CHANGED
@@ -103,6 +103,12 @@
103
103
  :default: true
104
104
  :procname: val_as_bool
105
105
 
106
+ - :opt_name: command_substitution_name_capture_group
107
+ :env_var: MDE_COMMAND_SUBSTITUTION_NAME_CAPTURE_GROUP
108
+ :description: command_substitution_name_capture_group
109
+ :default: command
110
+ :procname: val_as_str
111
+
106
112
  - :opt_name: command_substitution_regexp
107
113
  :env_var: MDE_COMMAND_SUBSTITUTION_REGEXP
108
114
  :description: command_substitution_regexp
@@ -207,12 +213,6 @@
207
213
  :default: "(document_shell)"
208
214
  :procname: val_as_str
209
215
 
210
- - :opt_name: document_load_ux_block_name
211
- :env_var: MDE_DOCUMENT_LOAD_UX_BLOCK_NAME
212
- :description: Name of UX block to load with the document
213
- :default: "\\[.*document_ux.*\\]"
214
- :procname: val_as_str
215
-
216
216
  - :opt_name: document_load_vars_block_name
217
217
  :env_var: MDE_DOCUMENT_LOAD_VARS_BLOCK_NAME
218
218
  :description: Name of Vars block to load with the document
@@ -1111,6 +1111,18 @@
1111
1111
  :default: ''
1112
1112
  :procname: open
1113
1113
 
1114
+ - :opt_name: option_expansion_expression_regexp
1115
+ :env_var: MDE_OPTION_EXPANSION_EXPRESSION_REGEXP
1116
+ :description: option_expansion_expression_regexp
1117
+ :default: '(?<expression>&{(?<payload>(?<option>[A-Z0-9a-z_]+)(?:\.(?<property>[A-Z0-9a-z_]+))?)})'
1118
+ :procname: val_as_str
1119
+
1120
+ - :opt_name: option_expansion_payload_regexp
1121
+ :env_var: MDE_OPTION_EXPANSION_PAYLOAD_REGEXP
1122
+ :description: option_expansion_payload_regexp
1123
+ :default: '^(?<option>[A-Z0-9a-z_]+)(?:\.(?<property>[A-Z0-9a-z_]+))?$'
1124
+ :procname: val_as_str
1125
+
1114
1126
  - :opt_name: output_assignment_begin
1115
1127
  :env_var: MDE_OUTPUT_ASSIGNMENT_BEGIN
1116
1128
  :description: Expression to match to start collecting lines
@@ -1663,9 +1675,15 @@
1663
1675
  :default: true
1664
1676
  :procname: val_as_bool
1665
1677
 
1666
- - :opt_name: variable_expression_regexp
1667
- :env_var: MDE_VARIABLE_EXPRESSION_REGEXP
1668
- :description: variable_expression_regexp
1678
+ - :opt_name: variable_expansion_name_capture_group
1679
+ :env_var: MDE_VARIABLE_EXPANSION_NAME_CAPTURE_GROUP
1680
+ :description: variable_expansion_name_capture_group
1681
+ :default: variable
1682
+ :procname: val_as_str
1683
+
1684
+ - :opt_name: variable_expansion_regexp
1685
+ :env_var: MDE_VARIABLE_EXPANSION_REGEXP
1686
+ :description: variable_expansion_regexp
1669
1687
  :default: "(?<expression>\\${(?<variable>[A-Z0-9a-z_]+)})"
1670
1688
  :procname: val_as_str
1671
1689
 
data/lib/menu.yml CHANGED
@@ -84,6 +84,11 @@
84
84
  :arg_name: BOOL
85
85
  :default: true
86
86
  :procname: val_as_bool
87
+ - :opt_name: command_substitution_name_capture_group
88
+ :env_var: MDE_COMMAND_SUBSTITUTION_NAME_CAPTURE_GROUP
89
+ :description: command_substitution_name_capture_group
90
+ :default: command
91
+ :procname: val_as_str
87
92
  - :opt_name: command_substitution_regexp
88
93
  :env_var: MDE_COMMAND_SUBSTITUTION_REGEXP
89
94
  :description: command_substitution_regexp
@@ -172,11 +177,6 @@
172
177
  :description: Name of shell block to load with the document
173
178
  :default: "(document_shell)"
174
179
  :procname: val_as_str
175
- - :opt_name: document_load_ux_block_name
176
- :env_var: MDE_DOCUMENT_LOAD_UX_BLOCK_NAME
177
- :description: Name of UX block to load with the document
178
- :default: "\\[.*document_ux.*\\]"
179
- :procname: val_as_str
180
180
  - :opt_name: document_load_vars_block_name
181
181
  :env_var: MDE_DOCUMENT_LOAD_VARS_BLOCK_NAME
182
182
  :description: Name of Vars block to load with the document
@@ -934,6 +934,16 @@
934
934
  :arg_name: OPEN
935
935
  :default: ''
936
936
  :procname: open
937
+ - :opt_name: option_expansion_expression_regexp
938
+ :env_var: MDE_OPTION_EXPANSION_EXPRESSION_REGEXP
939
+ :description: option_expansion_expression_regexp
940
+ :default: "(?<expression>&{(?<payload>(?<option>[A-Z0-9a-z_]+)(?:\\.(?<property>[A-Z0-9a-z_]+))?)})"
941
+ :procname: val_as_str
942
+ - :opt_name: option_expansion_payload_regexp
943
+ :env_var: MDE_OPTION_EXPANSION_PAYLOAD_REGEXP
944
+ :description: option_expansion_payload_regexp
945
+ :default: "^(?<option>[A-Z0-9a-z_]+)(?:\\.(?<property>[A-Z0-9a-z_]+))?$"
946
+ :procname: val_as_str
937
947
  - :opt_name: output_assignment_begin
938
948
  :env_var: MDE_OUTPUT_ASSIGNMENT_BEGIN
939
949
  :description: Expression to match to start collecting lines
@@ -1419,9 +1429,14 @@
1419
1429
  :arg_name: BOOL
1420
1430
  :default: true
1421
1431
  :procname: val_as_bool
1422
- - :opt_name: variable_expression_regexp
1423
- :env_var: MDE_VARIABLE_EXPRESSION_REGEXP
1424
- :description: variable_expression_regexp
1432
+ - :opt_name: variable_expansion_name_capture_group
1433
+ :env_var: MDE_VARIABLE_EXPANSION_NAME_CAPTURE_GROUP
1434
+ :description: variable_expansion_name_capture_group
1435
+ :default: variable
1436
+ :procname: val_as_str
1437
+ - :opt_name: variable_expansion_regexp
1438
+ :env_var: MDE_VARIABLE_EXPANSION_REGEXP
1439
+ :description: variable_expansion_regexp
1425
1440
  :default: "(?<expression>\\${(?<variable>[A-Z0-9a-z_]+)})"
1426
1441
  :procname: val_as_str
1427
1442
  - :opt_name: vars_block_filename_view
data/lib/namer.rb CHANGED
@@ -9,9 +9,7 @@ class Hash
9
9
  # block name in commands and documents
10
10
  def pub_name(**kwargs)
11
11
  full = fetch(:nickname, nil) || fetch(:oname, nil)
12
- full&.to_s&.pub_name(**kwargs).tap do |ret|
13
- pp [__LINE__, 'Hash.pub_name() ->', ret] if $pd
14
- end
12
+ full&.to_s&.pub_name(**kwargs)
15
13
  end
16
14
  end
17
15
 
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env -S bundle exec ruby
2
+ # frozen_string_literal: true
3
+
4
+ # encoding=utf-8
5
+
6
+ # == ValueOrException
7
+ #
8
+ # Encapsulates either a valid String value or an exception Symbol,
9
+ # and provides methods to query and update the stored value.
10
+ #
11
+ # === Examples
12
+ #
13
+ # obj = ValueOrException.new("foo")
14
+ # obj.valid? # => true
15
+ # obj.exception? # => false
16
+ # obj.get # => "foo"
17
+ #
18
+ # obj.set(:error) # now holds an exception
19
+ # obj.valid? # => false
20
+ # obj.exception? # => true
21
+ # obj.get # => :error
22
+ class ValueOrException
23
+ # @return [String, Symbol] the stored value or exception
24
+ attr_accessor :message
25
+ attr_reader :value
26
+
27
+ # @param [String, Symbol] val a valid string or an exception symbol
28
+ # @raise [ArgumentError] if val is neither String nor Symbol
29
+ def initialize(val, message = nil)
30
+ validate!(val)
31
+ @value = val
32
+ @message = message
33
+ end
34
+
35
+ # @return [Boolean] true if the stored value is a Symbol (an exception)
36
+ def exception?
37
+ value.is_a?(Symbol)
38
+ end
39
+
40
+ # @return [Boolean] true if the stored value is a String (a valid value)
41
+ def valid?
42
+ !exception?
43
+ end
44
+
45
+ # Retrieve the current stored value or exception.
46
+ #
47
+ # @return [String, Symbol]
48
+ def get
49
+ valid? ? value : message
50
+ end
51
+
52
+ # Update the stored value or exception.
53
+ #
54
+ # @param [String, Symbol] new_val the new value or exception
55
+ # @raise [ArgumentError] if new_val is neither String nor Symbol
56
+ def set(new_val)
57
+ validate!(new_val)
58
+ @value = new_val
59
+ end
60
+
61
+ def to_s
62
+ valid? ? value.to_s : message
63
+ end
64
+
65
+ private
66
+
67
+ # Ensure the provided value is of an allowed type.
68
+ #
69
+ # @param [Object] val the value to check
70
+ # @raise [ArgumentError] if val is not a String or Symbol
71
+ def validate!(val)
72
+ return if val.is_a?(String) || val.is_a?(Symbol)
73
+
74
+ raise ArgumentError, "Expected a String or Symbol, got #{val.class}"
75
+ end
76
+ end