markdown_exec 2.2.0 → 2.4.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/.rubocop.yml +16 -4
- data/CHANGELOG.md +28 -0
- data/Gemfile.lock +1 -1
- data/Rakefile +32 -8
- data/bats/bats.bats +33 -0
- data/bats/block-types.bats +56 -0
- data/bats/cli.bats +74 -0
- data/bats/fail.bats +11 -0
- data/bats/history.bats +34 -0
- data/bats/markup.bats +66 -0
- data/bats/mde.bats +29 -0
- data/bats/options.bats +92 -0
- data/bats/test_helper.bash +152 -0
- data/bin/tab_completion.sh +44 -20
- data/docs/dev/block-type-opts.md +10 -0
- data/docs/dev/block-type-port.md +24 -0
- data/docs/dev/block-type-vars.md +7 -0
- data/docs/dev/pass-through-arguments.md +8 -0
- data/docs/dev/specs-import.md +9 -0
- data/docs/dev/specs.md +83 -0
- data/docs/dev/text-decoration.md +7 -0
- data/examples/bash-blocks.md +4 -4
- data/examples/block-names.md +40 -5
- data/examples/import0.md +23 -0
- data/examples/import1.md +13 -0
- data/examples/link-blocks-vars.md +3 -3
- data/examples/opts-blocks-require.md +6 -6
- data/examples/table-markup.md +31 -0
- data/examples/text-markup.md +58 -0
- data/examples/vars-blocks.md +2 -2
- data/examples/wrap.md +87 -9
- data/lib/ansi_formatter.rb +12 -6
- data/lib/ansi_string.rb +153 -0
- data/lib/argument_processor.rb +160 -0
- data/lib/cached_nested_file_reader.rb +4 -2
- data/lib/ce_get_cost_and_usage.rb +4 -3
- data/lib/cli.rb +1 -1
- data/lib/colorize.rb +41 -0
- data/lib/constants.rb +17 -0
- data/lib/directory_searcher.rb +4 -2
- data/lib/doh.rb +190 -0
- data/lib/env.rb +1 -1
- data/lib/exceptions.rb +9 -6
- data/lib/fcb.rb +0 -199
- data/lib/filter.rb +18 -5
- data/lib/find_files.rb +8 -3
- data/lib/format_table.rb +406 -0
- data/lib/hash_delegator.rb +939 -611
- data/lib/hierarchy_string.rb +221 -0
- data/lib/input_sequencer.rb +19 -11
- data/lib/instance_method_wrapper.rb +2 -1
- data/lib/layered_hash.rb +143 -0
- data/lib/link_history.rb +22 -8
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/markdown_exec.rb +420 -165
- data/lib/mdoc.rb +38 -38
- data/lib/menu.src.yml +832 -680
- data/lib/menu.yml +814 -689
- data/lib/namer.rb +6 -12
- data/lib/object_present.rb +1 -1
- data/lib/option_value.rb +7 -3
- data/lib/poly.rb +33 -14
- data/lib/resize_terminal.rb +60 -52
- data/lib/saved_assets.rb +45 -34
- data/lib/saved_files_matcher.rb +6 -3
- data/lib/streams_out.rb +7 -1
- data/lib/table_extractor.rb +166 -0
- data/lib/tap.rb +5 -6
- data/lib/text_analyzer.rb +236 -0
- metadata +28 -3
- data/lib/std_out_err_logger.rb +0 -119
data/lib/markdown_exec.rb
CHANGED
@@ -115,24 +115,29 @@ module MarkdownExec
|
|
115
115
|
|
116
116
|
# A class that generates a histogram bar in terminal using xterm-256 color codes.
|
117
117
|
class Histogram
|
118
|
-
# Generates and prints a histogram bar for a given value within a
|
118
|
+
# Generates and prints a histogram bar for a given value within a
|
119
|
+
# specified range and width, with an option for inverse display.
|
119
120
|
# @param integer_value [Integer] the value to represent in the histogram
|
120
121
|
# @param min [Integer] the minimum value of the range
|
121
122
|
# @param max [Integer] the maximum value of the range
|
122
123
|
# @param width [Integer] the total width of the histogram in characters
|
123
|
-
# @param inverse [Boolean] whether the histogram is
|
124
|
+
# @param inverse [Boolean] whether the histogram is
|
125
|
+
# displayed in inverse order (right to left)
|
124
126
|
def self.display(integer_value, min, max, width, inverse: false)
|
125
127
|
return if max <= min # Ensure the range is valid
|
126
128
|
|
127
129
|
# Normalize the value within the range 0 to 1
|
128
|
-
normalized_value = [
|
130
|
+
normalized_value = [
|
131
|
+
0,
|
132
|
+
[(integer_value - min).to_f / (max - min), 1].min
|
133
|
+
].max
|
129
134
|
|
130
135
|
# Calculate how many characters should be filled
|
131
136
|
filled_length = (normalized_value * width).round
|
132
137
|
|
133
138
|
# # Generate the histogram bar using xterm-256 colors (color code 42 is green)
|
134
139
|
# filled_bar = "\e[48;5;42m" + ' ' * filled_length + "\e[0m"
|
135
|
-
filled_bar = ('¤' * filled_length).fg_rgbh_AF_AF_00
|
140
|
+
filled_bar = AnsiString.new('¤' * filled_length).fg_rgbh_AF_AF_00
|
136
141
|
empty_bar = ' ' * (width - filled_length)
|
137
142
|
|
138
143
|
# Determine the order of filled and empty parts based on the inverse flag
|
@@ -146,25 +151,35 @@ module MarkdownExec
|
|
146
151
|
@o_color = :red
|
147
152
|
end
|
148
153
|
|
149
|
-
def build_menu(file_names, directory_names, found_in_block_names,
|
154
|
+
def build_menu(file_names, directory_names, found_in_block_names,
|
155
|
+
file_name_choices, choices_from_block_names)
|
150
156
|
choices = []
|
151
157
|
|
152
158
|
# Adding section title and data for file names
|
153
|
-
choices << {
|
159
|
+
choices << {
|
160
|
+
disabled: '',
|
161
|
+
name: AnsiString.new("in #{file_names[:section_title]}")
|
162
|
+
.send(@chrome_color)
|
163
|
+
}
|
154
164
|
choices += file_names[:data].map { |str| FileInMenu.for_menu(str) }
|
155
165
|
|
156
166
|
# Conditionally add directory names if data is present
|
157
|
-
|
158
|
-
choices << {
|
159
|
-
|
160
|
-
|
167
|
+
if directory_names[:data].any?
|
168
|
+
choices << {
|
169
|
+
disabled: '',
|
170
|
+
name: AnsiString.new("in #{directory_names[:section_title]}")
|
171
|
+
.send(@chrome_color)
|
172
|
+
}
|
173
|
+
choices += file_name_choices
|
161
174
|
end
|
162
175
|
|
163
176
|
# Adding found in block names
|
164
|
-
choices << {
|
165
|
-
|
166
|
-
|
167
|
-
|
177
|
+
choices << {
|
178
|
+
disabled: '',
|
179
|
+
name: AnsiString.new("in #{found_in_block_names[:section_title]}")
|
180
|
+
.send(@chrome_color)
|
181
|
+
}
|
182
|
+
choices += choices_from_block_names
|
168
183
|
|
169
184
|
choices
|
170
185
|
end
|
@@ -176,18 +191,22 @@ module MarkdownExec
|
|
176
191
|
{
|
177
192
|
section_title: 'directory names',
|
178
193
|
data: matched_directories,
|
179
|
-
formatted_text: [{ content:
|
180
|
-
|
181
|
-
|
194
|
+
formatted_text: [{ content:
|
195
|
+
AnsiFormatter.new(search_options).format_and_highlight_array(
|
196
|
+
matched_directories, highlight: [highlight_value]
|
197
|
+
) }]
|
182
198
|
}
|
183
199
|
end
|
184
200
|
|
185
201
|
def found_in_block_names(search_options, highlight_value,
|
186
202
|
formspec: '=%<index>4.d: %<line>s')
|
187
|
-
matched_contents = (
|
188
|
-
|
189
|
-
|
190
|
-
|
203
|
+
matched_contents = (
|
204
|
+
find_file_contents do |line|
|
205
|
+
read_block_name(line,
|
206
|
+
search_options[:fenced_start_and_end_regex],
|
207
|
+
search_options[:block_name_match],
|
208
|
+
search_options[:block_name_nick_match])
|
209
|
+
end).map.with_index do |(file, contents), index|
|
191
210
|
# [file, contents.map { |detail| format(formspec, detail.index, detail.line) }, index]
|
192
211
|
[file, contents.map do |detail|
|
193
212
|
format(formspec, { index: detail.index, line: detail.line })
|
@@ -196,13 +215,14 @@ module MarkdownExec
|
|
196
215
|
{
|
197
216
|
section_title: 'block names',
|
198
217
|
data: matched_contents.map(&:first),
|
199
|
-
formatted_text:
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
218
|
+
formatted_text:
|
219
|
+
matched_contents.map do |(file, details, index)|
|
220
|
+
{ header: format('- %3.d: %s', index + 1, file),
|
221
|
+
content: AnsiFormatter.new(search_options).format_and_highlight_array(
|
222
|
+
details,
|
223
|
+
highlight: [highlight_value]
|
224
|
+
) }
|
225
|
+
end,
|
206
226
|
matched_contents: matched_contents
|
207
227
|
}
|
208
228
|
end
|
@@ -212,9 +232,12 @@ module MarkdownExec
|
|
212
232
|
{
|
213
233
|
section_title: 'file names',
|
214
234
|
data: matched_files,
|
215
|
-
formatted_text:
|
216
|
-
|
217
|
-
|
235
|
+
formatted_text:
|
236
|
+
[{ content:
|
237
|
+
AnsiFormatter.new(search_options).format_and_highlight_array(
|
238
|
+
matched_files,
|
239
|
+
highlight: [highlight_value]
|
240
|
+
).join("\n") }]
|
218
241
|
}
|
219
242
|
end
|
220
243
|
|
@@ -263,20 +286,6 @@ module MarkdownExec
|
|
263
286
|
)
|
264
287
|
end
|
265
288
|
|
266
|
-
# :reek:UtilityFunction
|
267
|
-
def list_recent_output(saved_stdout_folder, saved_stdout_glob,
|
268
|
-
list_count)
|
269
|
-
SavedFilesMatcher.most_recent_list(saved_stdout_folder,
|
270
|
-
saved_stdout_glob, list_count)
|
271
|
-
end
|
272
|
-
|
273
|
-
# :reek:UtilityFunction
|
274
|
-
def list_recent_scripts(saved_script_folder, saved_script_glob,
|
275
|
-
list_count)
|
276
|
-
SavedFilesMatcher.most_recent_list(saved_script_folder,
|
277
|
-
saved_script_glob, list_count)
|
278
|
-
end
|
279
|
-
|
280
289
|
def warn_format(name, message, opts = {})
|
281
290
|
Exceptions.warn_format(
|
282
291
|
"CachedNestedFileReader.#{name} -- #{message}",
|
@@ -324,22 +333,52 @@ module MarkdownExec
|
|
324
333
|
}
|
325
334
|
end
|
326
335
|
|
336
|
+
def choices_from_block_names(value, found_in_block_names)
|
337
|
+
found_in_block_names[:matched_contents].map do |matched_contents|
|
338
|
+
filename, details, = matched_contents
|
339
|
+
nexo = AnsiFormatter.new(@options).format_and_highlight_array(
|
340
|
+
details,
|
341
|
+
highlight: [value]
|
342
|
+
)
|
343
|
+
[FileInMenu.for_menu(filename)] +
|
344
|
+
nexo.map do |str|
|
345
|
+
{ disabled: '', name: (' ' * 20) + str }
|
346
|
+
end
|
347
|
+
end.flatten
|
348
|
+
end
|
349
|
+
|
350
|
+
def choices_from_file_names(directory_names)
|
351
|
+
directory_names[:data].map do |dn|
|
352
|
+
find_files('*', [dn], exclude_dirs: true)
|
353
|
+
end.flatten(1).map { |str| FileInMenu.for_menu(str) }
|
354
|
+
end
|
355
|
+
|
327
356
|
public
|
328
357
|
|
329
358
|
## Determines the correct filename to use for searching files
|
330
359
|
#
|
331
|
-
def determine_filename(
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
360
|
+
def determine_filename(
|
361
|
+
specified_filename: nil, specified_folder: nil,
|
362
|
+
default_filename: nil, default_folder: nil, filetree: nil
|
363
|
+
)
|
364
|
+
File.join(
|
365
|
+
*(if specified_filename&.present?
|
366
|
+
if specified_filename.start_with?('/')
|
367
|
+
[specified_filename]
|
368
|
+
else
|
369
|
+
[specified_folder || default_folder, specified_filename]
|
370
|
+
end
|
371
|
+
elsif specified_folder&.present?
|
372
|
+
[specified_folder,
|
373
|
+
if filetree
|
374
|
+
@options[:md_filename_match]
|
375
|
+
else
|
376
|
+
@options[:md_filename_glob]
|
377
|
+
end]
|
378
|
+
else
|
379
|
+
[default_folder, default_filename]
|
380
|
+
end)
|
381
|
+
)
|
343
382
|
end
|
344
383
|
|
345
384
|
private
|
@@ -351,82 +390,57 @@ module MarkdownExec
|
|
351
390
|
# raise ArgumentError, error
|
352
391
|
# end
|
353
392
|
|
354
|
-
# Reports and executes block logic
|
355
|
-
def execute_block_logic(files)
|
356
|
-
@options[:filename] = select_document_if_multiple(files)
|
357
|
-
@options.document_inpseq
|
358
|
-
rescue StandardError
|
359
|
-
error_handler('execute_block_logic')
|
360
|
-
# rubocop:disable Style/RescueStandardError
|
361
|
-
rescue
|
362
|
-
pp $!, $@
|
363
|
-
exit 1
|
364
|
-
# rubocop:enable Style/RescueStandardError
|
365
|
-
end
|
366
|
-
|
367
393
|
## Executes the block specified in the options
|
368
394
|
#
|
369
395
|
def execute_block_with_error_handling
|
370
396
|
finalize_cli_argument_processing
|
371
|
-
execute_code_block_based_on_options(@options)
|
397
|
+
execute_code_block_based_on_options(@options, @options.run_state)
|
372
398
|
rescue FileMissingError
|
373
399
|
warn "File missing: #{$!}"
|
374
|
-
rescue StandardError
|
375
|
-
error_handler('execute_block_with_error_handling')
|
376
400
|
end
|
377
401
|
|
378
402
|
# Main method to execute a block based on options and block_name
|
379
|
-
def execute_code_block_based_on_options(options)
|
403
|
+
def execute_code_block_based_on_options(options, run_state)
|
380
404
|
options = calculated_options.merge(options)
|
381
405
|
update_options(options, over: false)
|
406
|
+
# recognize commands with an opt_name, no procname
|
407
|
+
return if execute_simple_commands(options)
|
382
408
|
|
383
|
-
|
384
|
-
doc_glob: -> { @fout.fout options[:md_filename_glob] },
|
385
|
-
# list_blocks: -> { list_blocks },
|
386
|
-
list_default_env: -> { @fout.fout_list list_default_env },
|
387
|
-
list_default_yaml: -> { @fout.fout_list list_default_yaml },
|
388
|
-
list_docs: -> { @fout.fout_list files },
|
389
|
-
list_recent_output: -> {
|
390
|
-
@fout.fout_list list_recent_output(
|
391
|
-
@options[:saved_stdout_folder],
|
392
|
-
@options[:saved_stdout_glob], @options[:list_count]
|
393
|
-
)
|
394
|
-
},
|
395
|
-
list_recent_scripts: -> {
|
396
|
-
@fout.fout_list list_recent_scripts(
|
397
|
-
options[:saved_script_folder],
|
398
|
-
options[:saved_script_glob], options[:list_count]
|
399
|
-
)
|
400
|
-
},
|
401
|
-
pwd: -> { @fout.fout File.expand_path('..', __dir__) },
|
402
|
-
run_last_script: -> { run_last_script },
|
403
|
-
tab_completions: -> { @fout.fout tab_completions },
|
404
|
-
menu_export: -> { @fout.fout menu_export }
|
405
|
-
}
|
406
|
-
|
407
|
-
return if execute_simple_commands(simple_commands)
|
408
|
-
|
409
|
-
files = opts_prepare_file_list(options)
|
410
|
-
execute_block_logic(files)
|
409
|
+
mde_vux_main_loop(opts_prepare_file_list(options))
|
411
410
|
return unless @options[:output_saved_script_filename]
|
412
411
|
|
413
|
-
@fout.fout "script_block_name: #{
|
414
|
-
@fout.fout "s_save_filespec: #{
|
415
|
-
rescue StandardError
|
416
|
-
error_handler('execute_code_block_based_on_options')
|
412
|
+
@fout.fout "script_block_name: #{run_state.script_block_name}"
|
413
|
+
@fout.fout "s_save_filespec: #{run_state.saved_filespec}"
|
417
414
|
end
|
418
415
|
|
419
416
|
# Executes command based on the provided option keys
|
420
|
-
def execute_simple_commands(
|
421
|
-
simple_commands.
|
422
|
-
if @options[key]
|
423
|
-
|
417
|
+
def execute_simple_commands(options)
|
418
|
+
simple_commands(options).each do |key, proc|
|
419
|
+
if @options[key].is_a?(TrueClass) || @options[key].present?
|
420
|
+
proc.call
|
424
421
|
return true
|
425
422
|
end
|
426
423
|
end
|
427
424
|
false
|
428
425
|
end
|
429
426
|
|
427
|
+
# Extracts all lines matching the given regular expression from a file.
|
428
|
+
#
|
429
|
+
# @param file_path [String] The path to the file to be searched.
|
430
|
+
# @param pattern [Regexp] The regular expression pattern to match.
|
431
|
+
# @return [Array<String>] An array of lines from the file that match the pattern.
|
432
|
+
def extract_lines_matching(file_path, pattern)
|
433
|
+
matching_lines = []
|
434
|
+
|
435
|
+
File.open(file_path, 'r') do |file|
|
436
|
+
file.each_line do |line|
|
437
|
+
matching_lines << line if line =~ pattern
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
matching_lines
|
442
|
+
end
|
443
|
+
|
430
444
|
## post-parse options configuration
|
431
445
|
#
|
432
446
|
def finalize_cli_argument_processing(rest = @rest)
|
@@ -444,9 +458,6 @@ module MarkdownExec
|
|
444
458
|
end
|
445
459
|
end
|
446
460
|
|
447
|
-
## position 1: block name (optional)
|
448
|
-
#
|
449
|
-
@options[:block_name] = nil
|
450
461
|
@options[:input_cli_rest] = @rest
|
451
462
|
rescue FileMissingError
|
452
463
|
warn_format('finalize_cli_argument_processing',
|
@@ -464,10 +475,36 @@ module MarkdownExec
|
|
464
475
|
:menu_chrome_color)}"
|
465
476
|
searcher = SearchResultsReport.new(value, [find_path])
|
466
477
|
file_names = searcher.file_names(options, value)
|
467
|
-
found_in_block_names = searcher.found_in_block_names(options, value,
|
478
|
+
found_in_block_names = searcher.found_in_block_names(options, value,
|
479
|
+
formspec: '%<line>s')
|
468
480
|
directory_names = searcher.directory_names(options, value)
|
469
481
|
|
470
482
|
### search in file contents (block names, chrome, or text)
|
483
|
+
found_report(found_in_block_names, directory_names, file_names)
|
484
|
+
|
485
|
+
return { exit: true } unless execute_chosen_found
|
486
|
+
|
487
|
+
file_name_choices = choices_from_file_names(directory_names)
|
488
|
+
|
489
|
+
unless file_names[:data]&.count.positive? ||
|
490
|
+
file_name_choices&.count.positive? ||
|
491
|
+
found_in_block_names[:data]&.count.positive?
|
492
|
+
return :exit
|
493
|
+
end
|
494
|
+
|
495
|
+
## pick a document to open
|
496
|
+
#
|
497
|
+
found_files_build_menu_user_select(
|
498
|
+
file_names, directory_names, found_in_block_names,
|
499
|
+
file_name_choices,
|
500
|
+
choices_from_block_names(value, found_in_block_names)
|
501
|
+
)
|
502
|
+
{ exit: false }
|
503
|
+
end
|
504
|
+
|
505
|
+
def found_report(found_in_block_names,
|
506
|
+
directory_names,
|
507
|
+
file_names)
|
471
508
|
[found_in_block_names,
|
472
509
|
directory_names,
|
473
510
|
file_names].each do |data|
|
@@ -480,69 +517,155 @@ module MarkdownExec
|
|
480
517
|
@fout.fout fi[:content] if fi[:content]
|
481
518
|
end
|
482
519
|
end
|
483
|
-
|
484
|
-
|
485
|
-
## pick a document to open
|
486
|
-
#
|
487
|
-
files_in_directories = directory_names[:data].map do |dn|
|
488
|
-
find_files('*', [dn], exclude_dirs: true)
|
489
|
-
end.flatten(1).map { |str| FileInMenu.for_menu(str) }
|
490
|
-
|
491
|
-
unless file_names[:data]&.count.positive? || files_in_directories&.count.positive? || found_in_block_names[:data]&.count.positive?
|
492
|
-
return { exit: true }
|
493
|
-
end
|
494
|
-
|
495
|
-
vbn = found_in_block_names[:matched_contents].map do |matched_contents|
|
496
|
-
filename, details, = matched_contents
|
497
|
-
nexo = AnsiFormatter.new(@options).format_and_highlight_array(
|
498
|
-
details,
|
499
|
-
highlight: [value]
|
500
|
-
)
|
501
|
-
[FileInMenu.for_menu(filename)] + nexo.map do |str|
|
502
|
-
{ disabled: '', name: (' ' * 20) + str }
|
503
|
-
end
|
504
|
-
end.flatten
|
520
|
+
end
|
505
521
|
|
506
|
-
|
507
|
-
|
522
|
+
def found_files_build_menu_user_select(
|
523
|
+
file_names, directory_names, found_in_block_names,
|
524
|
+
file_name_choices, choices_from_block_names
|
525
|
+
)
|
526
|
+
choices = MenuBuilder.new.build_menu(
|
527
|
+
file_names, directory_names, found_in_block_names,
|
528
|
+
file_name_choices, choices_from_block_names
|
529
|
+
)
|
508
530
|
|
509
531
|
@options[:filename] = FileInMenu.from_menu(
|
510
532
|
select_document_if_multiple(
|
511
533
|
choices,
|
512
|
-
prompt: options[:prompt_select_md].to_s +
|
534
|
+
prompt: options[:prompt_select_md].to_s +
|
535
|
+
AnsiString.new(' ¤ Age in months').fg_rgbh_AF_AF_00
|
513
536
|
)
|
514
537
|
)
|
515
|
-
|
538
|
+
end
|
539
|
+
|
540
|
+
def fout_list(list)
|
541
|
+
if @options[:list_output_format] == :yaml
|
542
|
+
@fout.fout list.to_yaml.sub(/^---\n/, '')
|
543
|
+
elsif @options[:list_output_format] == :json
|
544
|
+
@fout.fout list.to_json
|
545
|
+
else # :text
|
546
|
+
list.each do |item|
|
547
|
+
@fout.fout item
|
548
|
+
end
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
def history(probe: true)
|
553
|
+
if probe && @options[:probe].present?
|
554
|
+
probe_regexp = Regexp.new(@options[:probe], Regexp::IGNORECASE)
|
555
|
+
end
|
556
|
+
|
557
|
+
if @options[:sift].present?
|
558
|
+
sift_regexp = Regexp.new(@options[:sift], Regexp::IGNORECASE)
|
559
|
+
end
|
560
|
+
|
561
|
+
files_table_rows = @options.read_saved_assets_for_history_table
|
562
|
+
|
563
|
+
if sift_regexp
|
564
|
+
# Filter history to file names matching a pattern
|
565
|
+
files_table_rows.select! { |item| sift_regexp.match(item[:file]) }
|
566
|
+
end
|
567
|
+
|
568
|
+
if probe_regexp
|
569
|
+
# Filter history to files with lines matching a pattern
|
570
|
+
files_table_rows.each do |item|
|
571
|
+
item[:probe] = extract_lines_matching(item[:file], probe_regexp)
|
572
|
+
end
|
573
|
+
files_table_rows.select! { |item| item[:probe].count.positive? }
|
574
|
+
end
|
575
|
+
|
576
|
+
if files_table_rows.count.zero?
|
577
|
+
warn 'Nothing revealed.'
|
578
|
+
elsif probe || probe_regexp
|
579
|
+
if @options[:dig]
|
580
|
+
# Present menu of history
|
581
|
+
@options.register_console_attributes(@options)
|
582
|
+
@options.execute_history_select(
|
583
|
+
files_table_rows,
|
584
|
+
exit_prompt: @options[:prompt_exit],
|
585
|
+
pause_refresh: true,
|
586
|
+
stream: $stderr
|
587
|
+
)
|
588
|
+
elsif @options[:mine]
|
589
|
+
# List lines matched by probe
|
590
|
+
files_table_rows.each do |item|
|
591
|
+
@fout.fout(item[:file])
|
592
|
+
fout_list(item[:probe])
|
593
|
+
end
|
594
|
+
else
|
595
|
+
fout_list(files_table_rows.map(&:file))
|
596
|
+
end
|
597
|
+
else
|
598
|
+
@options.register_console_attributes(@options)
|
599
|
+
@options.execute_history_select(
|
600
|
+
files_table_rows,
|
601
|
+
exit_prompt: @options[:prompt_exit],
|
602
|
+
pause_refresh: true,
|
603
|
+
stream: $stderr
|
604
|
+
)
|
605
|
+
end
|
516
606
|
end
|
517
607
|
|
518
608
|
## Sets up the options and returns the parsed arguments
|
519
609
|
#
|
520
|
-
def
|
521
|
-
# @options = base_options
|
610
|
+
def initialize_parse_execute_cli
|
522
611
|
@options = HashDelegator.new(base_options)
|
523
|
-
|
612
|
+
@options.p_all_arguments = ARGV.dup # Duplicate ARGV to track original order
|
613
|
+
### should not include after '--'
|
524
614
|
read_configuration_file!(@options,
|
525
615
|
".#{MarkdownExec::APP_NAME.downcase}.yml")
|
526
616
|
|
617
|
+
options_parsed = []
|
527
618
|
@option_parser = OptionParser.new do |opts|
|
528
619
|
executable_name = File.basename($PROGRAM_NAME)
|
529
620
|
opts.banner = [
|
530
621
|
"#{MarkdownExec::APP_NAME}" \
|
531
622
|
" - #{MarkdownExec::APP_DESC} (#{MarkdownExec::VERSION})",
|
532
|
-
"Usage: #{executable_name}
|
623
|
+
"Usage: #{executable_name}" \
|
624
|
+
' [(directory | file [block_name] | search_keyword)] [options]'
|
533
625
|
].join("\n")
|
534
626
|
|
535
627
|
menu_iter do |item|
|
536
|
-
opts_menu_option_append
|
628
|
+
opts_menu_option_append(opts, @options, item, options_parsed)
|
537
629
|
end
|
538
630
|
end
|
539
631
|
@option_parser.load
|
540
632
|
@option_parser.environment
|
541
|
-
@
|
542
|
-
@
|
633
|
+
@options.p_params = {}
|
634
|
+
@rest = @option_parser.parse!(arguments_for_mde, into: @options.p_params)
|
635
|
+
@options.p_options_parsed = options_parsed
|
636
|
+
@options.p_rest = @rest.dup
|
637
|
+
|
638
|
+
# Arguments for script follow ARGV, excluding arguments reserved for MDE, and separator.
|
639
|
+
# Parsed options have already been removed from ARGV.
|
640
|
+
@options.pass_args = ARGV[@rest.count + 1..]
|
641
|
+
|
543
642
|
@options.merge(@options.run_state.to_h)
|
643
|
+
end
|
544
644
|
|
545
|
-
|
645
|
+
def iter_source_blocks(source, &block)
|
646
|
+
case source
|
647
|
+
when 1
|
648
|
+
HashDelegator.new(@options).blocks_from_nested_files.each(&block)
|
649
|
+
when 2
|
650
|
+
blocks_in_file, menu_blocks, mdoc =
|
651
|
+
HashDelegator.new(@options)
|
652
|
+
.mdoc_menu_and_blocks_from_nested_files(LinkState.new)
|
653
|
+
blocks_in_file.each(&block)
|
654
|
+
when 3
|
655
|
+
blocks_in_file, menu_blocks, mdoc =
|
656
|
+
HashDelegator.new(@options)
|
657
|
+
.mdoc_menu_and_blocks_from_nested_files(LinkState.new)
|
658
|
+
menu_blocks.each(&block)
|
659
|
+
else
|
660
|
+
@options.iter_blocks_from_nested_files do |btype, fcb|
|
661
|
+
case btype
|
662
|
+
when :blocks
|
663
|
+
yield fcb
|
664
|
+
when :filter
|
665
|
+
%i[blocks]
|
666
|
+
end
|
667
|
+
end
|
668
|
+
end
|
546
669
|
end
|
547
670
|
|
548
671
|
##
|
@@ -558,8 +681,9 @@ module MarkdownExec
|
|
558
681
|
->(_) { exit }
|
559
682
|
when 'find', 'open'
|
560
683
|
->(value) {
|
561
|
-
exit if find_value(
|
562
|
-
|
684
|
+
exit if find_value(
|
685
|
+
value, execute_chosen_found: procname == 'open'
|
686
|
+
).fetch(:exit, false)
|
563
687
|
}
|
564
688
|
when 'help'
|
565
689
|
->(_) {
|
@@ -588,6 +712,8 @@ module MarkdownExec
|
|
588
712
|
lambda(&:to_i)
|
589
713
|
when 'val_as_str'
|
590
714
|
lambda(&:to_s)
|
715
|
+
when 'val_as_sym'
|
716
|
+
lambda(&:to_sym)
|
591
717
|
when 'version'
|
592
718
|
lambda { |_|
|
593
719
|
@fout.fout MarkdownExec::VERSION
|
@@ -598,7 +724,17 @@ module MarkdownExec
|
|
598
724
|
end
|
599
725
|
end
|
600
726
|
|
601
|
-
|
727
|
+
def list_blocks
|
728
|
+
message = @options[:list_blocks_message]
|
729
|
+
block_eval = @options[:list_blocks_eval]
|
730
|
+
|
731
|
+
list = []
|
732
|
+
iter_source_blocks(@options[:list_blocks_type]) do |block|
|
733
|
+
list << (block_eval.present? ? eval(block_eval) : block.send(message))
|
734
|
+
end
|
735
|
+
|
736
|
+
fout_list(list)
|
737
|
+
end
|
602
738
|
|
603
739
|
def list_default_env
|
604
740
|
menu_iter do |item|
|
@@ -639,6 +775,35 @@ module MarkdownExec
|
|
639
775
|
@options[:md_filename_glob]))
|
640
776
|
end
|
641
777
|
|
778
|
+
# :reek:UtilityFunction
|
779
|
+
def list_recent_output(saved_stdout_folder, saved_stdout_glob,
|
780
|
+
list_count)
|
781
|
+
SavedFilesMatcher.most_recent_list(saved_stdout_folder,
|
782
|
+
saved_stdout_glob, list_count)
|
783
|
+
end
|
784
|
+
|
785
|
+
# :reek:UtilityFunction
|
786
|
+
def list_recent_scripts(saved_script_folder, saved_script_glob,
|
787
|
+
list_count)
|
788
|
+
SavedFilesMatcher.most_recent_list(saved_script_folder,
|
789
|
+
saved_script_glob, list_count)
|
790
|
+
end
|
791
|
+
|
792
|
+
# Reports and executes block logic
|
793
|
+
def mde_vux_main_loop(files)
|
794
|
+
@options[:filename] = select_document_if_multiple(files)
|
795
|
+
@options.vux_main_loop do |type, data|
|
796
|
+
case type
|
797
|
+
when :command_names
|
798
|
+
simple_commands(data).keys
|
799
|
+
when :call_proc
|
800
|
+
simple_commands(data[0])[data[1]].call
|
801
|
+
else
|
802
|
+
raise
|
803
|
+
end
|
804
|
+
end
|
805
|
+
end
|
806
|
+
|
642
807
|
private
|
643
808
|
|
644
809
|
##
|
@@ -668,37 +833,88 @@ module MarkdownExec
|
|
668
833
|
end.to_yaml
|
669
834
|
end
|
670
835
|
|
671
|
-
def opts_menu_option_append(opts, options, item)
|
836
|
+
def opts_menu_option_append(opts, options, item, options_parsed)
|
672
837
|
return unless item[:long_name].present? || item[:short_name].present?
|
673
838
|
|
674
|
-
|
839
|
+
optname = "-#{item[:short_name]}"
|
840
|
+
switches = [
|
841
|
+
# - argument style = :NONE, :REQUIRED, :OPTIONAL
|
842
|
+
case item[:procname]&.to_s
|
843
|
+
when nil
|
844
|
+
:NONE
|
845
|
+
when *%w[val_as_bool val_as_int val_as_str val_as_sym]
|
846
|
+
:REQUIRED
|
847
|
+
else # debug, exit, find, help, how, open, path, show_config, version
|
848
|
+
nil
|
849
|
+
end,
|
850
|
+
|
675
851
|
# - long name
|
676
852
|
if item[:long_name].present?
|
677
|
-
"--#{item[:long_name]}
|
853
|
+
optname = "--#{item[:long_name]}"
|
854
|
+
"--#{item[:long_name]}" \
|
855
|
+
"#{item[:arg_name].present? ? " #{item[:arg_name]}" : ''}"
|
678
856
|
end,
|
679
857
|
|
680
858
|
# - short name
|
681
|
-
item[:short_name].present?
|
859
|
+
if item[:short_name].present?
|
860
|
+
"-#{item[:short_name]}" \
|
861
|
+
"#{item[:arg_name].present? ? " #{item[:arg_name]}" : ''}"
|
862
|
+
end,
|
682
863
|
|
683
864
|
# - description and default
|
684
|
-
[
|
685
|
-
|
865
|
+
[
|
866
|
+
item[:description],
|
867
|
+
("[#{value_for_cli item[:default]}]" if item[:default].present?)
|
868
|
+
].compact.join(' '),
|
869
|
+
|
870
|
+
# - type coercion
|
871
|
+
case item[:procname]&.to_s
|
872
|
+
when nil
|
873
|
+
nil
|
874
|
+
when 'val_as_bool'
|
875
|
+
item[:default] ? FalseClass : TrueClass # use sets to desired value
|
876
|
+
when 'val_as_int'
|
877
|
+
Integer
|
878
|
+
else # str, sym
|
879
|
+
# String
|
880
|
+
nil
|
881
|
+
end,
|
882
|
+
# Date – Anything accepted by Date.parse
|
883
|
+
# DateTime – Anything accepted by DateTime.parse
|
884
|
+
# Time – Anything accepted by Time.httpdate or Time.parse
|
885
|
+
# URI – Anything accepted by URI.parse
|
886
|
+
# Shellwords – Anything accepted by Shellwords.shellwords
|
887
|
+
# String – Any non-empty string
|
888
|
+
# Integer – Any integer. Will convert octal. (e.g. 124, -3, 040)
|
889
|
+
# Float – Any float. (e.g. 10, 3.14, -100E+13)
|
890
|
+
# Numeric – Any integer, float, or rational (1, 3.4, 1/3)
|
891
|
+
# DecimalInteger – Like Integer, but no octal format.
|
892
|
+
# OctalInteger – Like Integer, but no decimal format.
|
893
|
+
# DecimalNumeric – Decimal integer or float.
|
894
|
+
# TrueClass – Accepts ‘+, yes, true, -, no, false’ and defaults as true
|
895
|
+
# FalseClass – Same as TrueClass, but defaults to false
|
896
|
+
# Array – Strings separated by ‘,’ (e.g. 1,2,3)
|
897
|
+
# Regexp – Regular expressions. Also includes options.
|
686
898
|
|
687
899
|
# apply proccode, if present, to value
|
688
900
|
# save value to options hash if option is named
|
689
901
|
#
|
690
902
|
lambda { |value|
|
903
|
+
name = item[:long_name]&.present? ? '--' + item[:long_name].to_s : '-' + item[:short_name].to_s
|
904
|
+
options_parsed << item.merge(name: name, value: value)
|
691
905
|
(item[:proccode] ? item[:proccode].call(value) : value).tap do |converted|
|
692
906
|
options[item[:opt_name]] = converted if item[:opt_name]
|
693
907
|
end
|
694
908
|
}
|
695
|
-
].compact
|
909
|
+
].compact
|
910
|
+
opts.on(*switches)
|
696
911
|
end
|
697
912
|
|
698
913
|
def opts_prepare_file_list(options)
|
699
914
|
list_files_specified(
|
700
915
|
determine_filename(
|
701
|
-
specified_filename:
|
916
|
+
specified_filename:
|
917
|
+
options[:filename]&.present? ? options[:filename] : nil,
|
702
918
|
specified_folder: options[:path],
|
703
919
|
default_filename: 'README.md',
|
704
920
|
default_folder: '.'
|
@@ -717,12 +933,21 @@ module MarkdownExec
|
|
717
933
|
public
|
718
934
|
|
719
935
|
def run
|
720
|
-
|
936
|
+
initialize_parse_execute_cli
|
721
937
|
execute_block_with_error_handling
|
938
|
+
rescue BlockMissing
|
939
|
+
warn 'Block missing'
|
940
|
+
exit 1
|
941
|
+
rescue AppInterrupt, TTY::Reader::InputInterrupt, BlockMissing
|
942
|
+
warn 'Exiting...' if $DEBUG
|
943
|
+
exit 1
|
722
944
|
rescue StandardError
|
723
945
|
error_handler('run')
|
724
|
-
|
725
|
-
|
946
|
+
# rubocop:disable Style/RescueStandardError
|
947
|
+
rescue
|
948
|
+
warn 'Exiting...' if $DEBUG
|
949
|
+
exit 1
|
950
|
+
# rubocop:enable Style/RescueStandardError
|
726
951
|
end
|
727
952
|
|
728
953
|
private
|
@@ -734,7 +959,7 @@ module MarkdownExec
|
|
734
959
|
|
735
960
|
saved_name_split filename
|
736
961
|
@options[:save_executed_script] = false
|
737
|
-
@options.
|
962
|
+
@options.vux_main_loop
|
738
963
|
rescue StandardError
|
739
964
|
error_handler('run_last_script')
|
740
965
|
end
|
@@ -756,8 +981,11 @@ module MarkdownExec
|
|
756
981
|
|
757
982
|
opts = options.dup
|
758
983
|
select_option_or_exit(
|
759
|
-
HashDelegator.new(@options)
|
760
|
-
|
984
|
+
HashDelegator.new(@options)
|
985
|
+
.string_send_color(
|
986
|
+
prompt,
|
987
|
+
:prompt_color_after_script_execution
|
988
|
+
),
|
761
989
|
files,
|
762
990
|
opts.merge(per_page: opts[:select_page_height])
|
763
991
|
)
|
@@ -770,6 +998,33 @@ module MarkdownExec
|
|
770
998
|
)&.fetch(:selected)
|
771
999
|
end
|
772
1000
|
|
1001
|
+
def simple_commands(options)
|
1002
|
+
{
|
1003
|
+
doc_glob: -> { @fout.fout options[:md_filename_glob] },
|
1004
|
+
history: -> { history },
|
1005
|
+
list_blocks: -> { list_blocks },
|
1006
|
+
list_default_env: -> { @fout.fout_list list_default_env },
|
1007
|
+
list_default_yaml: -> { @fout.fout_list list_default_yaml },
|
1008
|
+
list_docs: -> { @fout.fout_list opts_prepare_file_list(options) },
|
1009
|
+
list_recent_output: -> {
|
1010
|
+
@fout.fout_list list_recent_output(
|
1011
|
+
@options[:saved_stdout_folder],
|
1012
|
+
@options[:saved_stdout_glob], @options[:list_count]
|
1013
|
+
)
|
1014
|
+
},
|
1015
|
+
list_recent_scripts: -> {
|
1016
|
+
@fout.fout_list list_recent_scripts(
|
1017
|
+
options[:saved_script_folder],
|
1018
|
+
options[:saved_script_glob], options[:list_count]
|
1019
|
+
)
|
1020
|
+
},
|
1021
|
+
menu_export: -> { @fout.fout menu_export },
|
1022
|
+
pwd: -> { @fout.fout File.expand_path('..', __dir__) },
|
1023
|
+
run_last_script: -> { run_last_script },
|
1024
|
+
tab_completions: -> { @fout.fout tab_completions }
|
1025
|
+
}
|
1026
|
+
end
|
1027
|
+
|
773
1028
|
public
|
774
1029
|
|
775
1030
|
def tab_completions(data = menu_for_optparse)
|