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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +33 -1
- data/Gemfile.lock +1 -1
- data/Rakefile +0 -33
- data/bats/bats.bats +2 -0
- data/bats/block-type-link.bats +1 -1
- data/bats/block-type-ux-allowed.bats +2 -2
- data/bats/block-type-ux-default.bats +8 -0
- data/bats/block-type-ux-invalid.bats +1 -1
- data/bats/{block-type-ux-preconditions.bats → block-type-ux-required-variables.bats} +1 -1
- data/bats/block-type-ux-row-format.bats +1 -1
- data/bats/block-type-ux-sources.bats +36 -0
- data/bats/border.bats +1 -1
- data/bats/cli.bats +2 -2
- data/bats/command-substitution-options.bats +14 -0
- data/bats/command-substitution.bats +1 -1
- data/bats/fail.bats +5 -2
- data/bats/indented-block-type-vars.bats +1 -1
- data/bats/markup.bats +1 -1
- data/bats/option-expansion.bats +8 -0
- data/bats/table-column-truncate.bats +1 -1
- data/bats/test_helper.bash +50 -5
- data/docs/dev/bats-document-configuration.md +1 -1
- data/docs/dev/block-type-ux-allowed.md +5 -5
- data/docs/dev/block-type-ux-auto.md +9 -5
- data/docs/dev/block-type-ux-chained.md +4 -2
- data/docs/dev/block-type-ux-default.md +42 -0
- data/docs/dev/block-type-ux-echo-hash.md +6 -1
- data/docs/dev/block-type-ux-echo.md +3 -1
- data/docs/dev/block-type-ux-exec.md +3 -4
- data/docs/dev/block-type-ux-hidden.md +3 -0
- data/docs/dev/block-type-ux-require.md +9 -18
- data/docs/dev/{block-type-ux-preconditions.md → block-type-ux-required-variables.md} +1 -2
- data/docs/dev/block-type-ux-row-format.md +3 -4
- data/docs/dev/block-type-ux-sources.md +57 -0
- data/docs/dev/block-type-ux-transform.md +0 -4
- data/docs/dev/command-substitution-options.md +61 -0
- data/docs/dev/indented-block-type-vars.md +1 -0
- data/docs/dev/menu-pagination-indent.md +123 -0
- data/docs/dev/menu-pagination.md +111 -0
- data/docs/dev/option-expansion.md +10 -0
- data/lib/ansi_formatter.rb +2 -0
- data/lib/block_cache.rb +197 -0
- data/lib/command_result.rb +57 -0
- data/lib/constants.rb +18 -0
- data/lib/error_reporting.rb +38 -0
- data/lib/evaluate_shell_expressions.rb +43 -18
- data/lib/fcb.rb +114 -11
- data/lib/hash_delegator.rb +595 -359
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/markdown_exec.rb +136 -45
- data/lib/mdoc.rb +74 -23
- data/lib/menu.src.yml +27 -9
- data/lib/menu.yml +23 -8
- data/lib/namer.rb +1 -3
- data/lib/value_or_exception.rb +76 -0
- metadata +18 -4
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
|
-
|
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
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
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
|
-
|
182
|
-
|
183
|
-
|
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
|
-
|
191
|
-
|
192
|
-
|
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(
|
366
|
-
end.flatten(1).map
|
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
|
806
|
-
menu_item
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
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
|
1053
|
-
require 'bundler/setup'
|
1054
|
-
Bundler.require(:default)
|
1094
|
+
return if $PROGRAM_NAME != __FILE__
|
1055
1095
|
|
1056
|
-
|
1096
|
+
require 'bundler/setup'
|
1097
|
+
Bundler.require(:default)
|
1057
1098
|
|
1058
|
-
|
1059
|
-
def test_select_block
|
1060
|
-
blocks = [block1, block2]
|
1061
|
-
menu = [m1, m2]
|
1099
|
+
require 'minitest/autorun'
|
1062
1100
|
|
1063
|
-
|
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
|
-
|
1066
|
-
|
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 #
|
1069
|
-
end #
|
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 =
|
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 |
|
181
|
-
|
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
|
-
|
223
|
+
table_not_split.select { |fcb|
|
224
|
+
fcb.code_name_included?(wrap_before, wrap)
|
225
|
+
}
|
184
226
|
end.flatten(1) +
|
185
|
-
[
|
186
|
-
(
|
227
|
+
[fcb] +
|
228
|
+
(fcb[:wraps] || []).reverse.map do |wrap|
|
187
229
|
wrap_after = wrap.sub('}', '-after}') ### hardcoded wrap name
|
188
|
-
|
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
|
-
|
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(
|
409
|
-
|
450
|
+
def collect_dependencies(block: nil, memo: {}, source: nil)
|
451
|
+
if block.nil?
|
452
|
+
return memo unless source
|
410
453
|
|
411
|
-
|
412
|
-
|
413
|
-
|
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[
|
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
|
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(
|
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:
|
1667
|
-
:env_var:
|
1668
|
-
:description:
|
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:
|
1423
|
-
:env_var:
|
1424
|
-
:description:
|
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)
|
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
|