markdown_exec 2.3.0 → 2.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +11 -2
- data/CHANGELOG.md +19 -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 +2 -2
- 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 +39 -11
- 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 +888 -603
- data/lib/hierarchy_string.rb +113 -25
- data/lib/input_sequencer.rb +16 -10
- 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 +413 -165
- data/lib/mdoc.rb +27 -34
- data/lib/menu.src.yml +825 -710
- data/lib/menu.yml +799 -703
- 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 +144 -8
- metadata +26 -3
- data/lib/std_out_err_logger.rb +0 -119
data/lib/markdown_exec.rb
CHANGED
@@ -115,12 +115,14 @@ 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
|
|
@@ -135,7 +137,7 @@ module MarkdownExec
|
|
135
137
|
|
136
138
|
# # Generate the histogram bar using xterm-256 colors (color code 42 is green)
|
137
139
|
# filled_bar = "\e[48;5;42m" + ' ' * filled_length + "\e[0m"
|
138
|
-
filled_bar = ('¤' * filled_length).fg_rgbh_AF_AF_00
|
140
|
+
filled_bar = AnsiString.new('¤' * filled_length).fg_rgbh_AF_AF_00
|
139
141
|
empty_bar = ' ' * (width - filled_length)
|
140
142
|
|
141
143
|
# Determine the order of filled and empty parts based on the inverse flag
|
@@ -150,26 +152,34 @@ module MarkdownExec
|
|
150
152
|
end
|
151
153
|
|
152
154
|
def build_menu(file_names, directory_names, found_in_block_names,
|
153
|
-
|
155
|
+
file_name_choices, choices_from_block_names)
|
154
156
|
choices = []
|
155
157
|
|
156
158
|
# Adding section title and data for file names
|
157
|
-
choices << {
|
158
|
-
|
159
|
+
choices << {
|
160
|
+
disabled: '',
|
161
|
+
name: AnsiString.new("in #{file_names[:section_title]}")
|
162
|
+
.send(@chrome_color)
|
163
|
+
}
|
159
164
|
choices += file_names[:data].map { |str| FileInMenu.for_menu(str) }
|
160
165
|
|
161
166
|
# Conditionally add directory names if data is present
|
162
|
-
|
163
|
-
choices << {
|
164
|
-
|
165
|
-
|
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
|
166
174
|
end
|
167
175
|
|
168
176
|
# Adding found in block names
|
169
|
-
choices << {
|
170
|
-
|
171
|
-
|
172
|
-
|
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
|
173
183
|
|
174
184
|
choices
|
175
185
|
end
|
@@ -181,18 +191,22 @@ module MarkdownExec
|
|
181
191
|
{
|
182
192
|
section_title: 'directory names',
|
183
193
|
data: matched_directories,
|
184
|
-
formatted_text: [{ content:
|
185
|
-
|
186
|
-
|
194
|
+
formatted_text: [{ content:
|
195
|
+
AnsiFormatter.new(search_options).format_and_highlight_array(
|
196
|
+
matched_directories, highlight: [highlight_value]
|
197
|
+
) }]
|
187
198
|
}
|
188
199
|
end
|
189
200
|
|
190
201
|
def found_in_block_names(search_options, highlight_value,
|
191
202
|
formspec: '=%<index>4.d: %<line>s')
|
192
|
-
matched_contents = (
|
193
|
-
|
194
|
-
|
195
|
-
|
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|
|
196
210
|
# [file, contents.map { |detail| format(formspec, detail.index, detail.line) }, index]
|
197
211
|
[file, contents.map do |detail|
|
198
212
|
format(formspec, { index: detail.index, line: detail.line })
|
@@ -201,13 +215,14 @@ module MarkdownExec
|
|
201
215
|
{
|
202
216
|
section_title: 'block names',
|
203
217
|
data: matched_contents.map(&:first),
|
204
|
-
formatted_text:
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
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,
|
211
226
|
matched_contents: matched_contents
|
212
227
|
}
|
213
228
|
end
|
@@ -217,9 +232,12 @@ module MarkdownExec
|
|
217
232
|
{
|
218
233
|
section_title: 'file names',
|
219
234
|
data: matched_files,
|
220
|
-
formatted_text:
|
221
|
-
|
222
|
-
|
235
|
+
formatted_text:
|
236
|
+
[{ content:
|
237
|
+
AnsiFormatter.new(search_options).format_and_highlight_array(
|
238
|
+
matched_files,
|
239
|
+
highlight: [highlight_value]
|
240
|
+
).join("\n") }]
|
223
241
|
}
|
224
242
|
end
|
225
243
|
|
@@ -268,20 +286,6 @@ module MarkdownExec
|
|
268
286
|
)
|
269
287
|
end
|
270
288
|
|
271
|
-
# :reek:UtilityFunction
|
272
|
-
def list_recent_output(saved_stdout_folder, saved_stdout_glob,
|
273
|
-
list_count)
|
274
|
-
SavedFilesMatcher.most_recent_list(saved_stdout_folder,
|
275
|
-
saved_stdout_glob, list_count)
|
276
|
-
end
|
277
|
-
|
278
|
-
# :reek:UtilityFunction
|
279
|
-
def list_recent_scripts(saved_script_folder, saved_script_glob,
|
280
|
-
list_count)
|
281
|
-
SavedFilesMatcher.most_recent_list(saved_script_folder,
|
282
|
-
saved_script_glob, list_count)
|
283
|
-
end
|
284
|
-
|
285
289
|
def warn_format(name, message, opts = {})
|
286
290
|
Exceptions.warn_format(
|
287
291
|
"CachedNestedFileReader.#{name} -- #{message}",
|
@@ -329,22 +333,52 @@ module MarkdownExec
|
|
329
333
|
}
|
330
334
|
end
|
331
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
|
+
|
332
356
|
public
|
333
357
|
|
334
358
|
## Determines the correct filename to use for searching files
|
335
359
|
#
|
336
|
-
def determine_filename(
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
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
|
+
)
|
348
382
|
end
|
349
383
|
|
350
384
|
private
|
@@ -356,82 +390,57 @@ module MarkdownExec
|
|
356
390
|
# raise ArgumentError, error
|
357
391
|
# end
|
358
392
|
|
359
|
-
# Reports and executes block logic
|
360
|
-
def execute_block_logic(files)
|
361
|
-
@options[:filename] = select_document_if_multiple(files)
|
362
|
-
@options.document_inpseq
|
363
|
-
rescue StandardError
|
364
|
-
error_handler('execute_block_logic')
|
365
|
-
# rubocop:disable Style/RescueStandardError
|
366
|
-
rescue
|
367
|
-
pp $!, $@
|
368
|
-
exit 1
|
369
|
-
# rubocop:enable Style/RescueStandardError
|
370
|
-
end
|
371
|
-
|
372
393
|
## Executes the block specified in the options
|
373
394
|
#
|
374
395
|
def execute_block_with_error_handling
|
375
396
|
finalize_cli_argument_processing
|
376
|
-
execute_code_block_based_on_options(@options)
|
397
|
+
execute_code_block_based_on_options(@options, @options.run_state)
|
377
398
|
rescue FileMissingError
|
378
399
|
warn "File missing: #{$!}"
|
379
|
-
rescue StandardError
|
380
|
-
error_handler('execute_block_with_error_handling')
|
381
400
|
end
|
382
401
|
|
383
402
|
# Main method to execute a block based on options and block_name
|
384
|
-
def execute_code_block_based_on_options(options)
|
403
|
+
def execute_code_block_based_on_options(options, run_state)
|
385
404
|
options = calculated_options.merge(options)
|
386
405
|
update_options(options, over: false)
|
406
|
+
# recognize commands with an opt_name, no procname
|
407
|
+
return if execute_simple_commands(options)
|
387
408
|
|
388
|
-
|
389
|
-
doc_glob: -> { @fout.fout options[:md_filename_glob] },
|
390
|
-
# list_blocks: -> { list_blocks },
|
391
|
-
list_default_env: -> { @fout.fout_list list_default_env },
|
392
|
-
list_default_yaml: -> { @fout.fout_list list_default_yaml },
|
393
|
-
list_docs: -> { @fout.fout_list files },
|
394
|
-
list_recent_output: -> {
|
395
|
-
@fout.fout_list list_recent_output(
|
396
|
-
@options[:saved_stdout_folder],
|
397
|
-
@options[:saved_stdout_glob], @options[:list_count]
|
398
|
-
)
|
399
|
-
},
|
400
|
-
list_recent_scripts: -> {
|
401
|
-
@fout.fout_list list_recent_scripts(
|
402
|
-
options[:saved_script_folder],
|
403
|
-
options[:saved_script_glob], options[:list_count]
|
404
|
-
)
|
405
|
-
},
|
406
|
-
pwd: -> { @fout.fout File.expand_path('..', __dir__) },
|
407
|
-
run_last_script: -> { run_last_script },
|
408
|
-
tab_completions: -> { @fout.fout tab_completions },
|
409
|
-
menu_export: -> { @fout.fout menu_export }
|
410
|
-
}
|
411
|
-
|
412
|
-
return if execute_simple_commands(simple_commands)
|
413
|
-
|
414
|
-
files = opts_prepare_file_list(options)
|
415
|
-
execute_block_logic(files)
|
409
|
+
mde_vux_main_loop(opts_prepare_file_list(options))
|
416
410
|
return unless @options[:output_saved_script_filename]
|
417
411
|
|
418
|
-
@fout.fout "script_block_name: #{
|
419
|
-
@fout.fout "s_save_filespec: #{
|
420
|
-
rescue StandardError
|
421
|
-
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}"
|
422
414
|
end
|
423
415
|
|
424
416
|
# Executes command based on the provided option keys
|
425
|
-
def execute_simple_commands(
|
426
|
-
simple_commands.
|
427
|
-
if @options[key]
|
428
|
-
|
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
|
429
421
|
return true
|
430
422
|
end
|
431
423
|
end
|
432
424
|
false
|
433
425
|
end
|
434
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
|
+
|
435
444
|
## post-parse options configuration
|
436
445
|
#
|
437
446
|
def finalize_cli_argument_processing(rest = @rest)
|
@@ -449,9 +458,6 @@ module MarkdownExec
|
|
449
458
|
end
|
450
459
|
end
|
451
460
|
|
452
|
-
## position 1: block name (optional)
|
453
|
-
#
|
454
|
-
@options[:block_name] = nil
|
455
461
|
@options[:input_cli_rest] = @rest
|
456
462
|
rescue FileMissingError
|
457
463
|
warn_format('finalize_cli_argument_processing',
|
@@ -474,6 +480,31 @@ module MarkdownExec
|
|
474
480
|
directory_names = searcher.directory_names(options, value)
|
475
481
|
|
476
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)
|
477
508
|
[found_in_block_names,
|
478
509
|
directory_names,
|
479
510
|
file_names].each do |data|
|
@@ -486,70 +517,155 @@ module MarkdownExec
|
|
486
517
|
@fout.fout fi[:content] if fi[:content]
|
487
518
|
end
|
488
519
|
end
|
489
|
-
|
490
|
-
|
491
|
-
## pick a document to open
|
492
|
-
#
|
493
|
-
files_in_directories = directory_names[:data].map do |dn|
|
494
|
-
find_files('*', [dn], exclude_dirs: true)
|
495
|
-
end.flatten(1).map { |str| FileInMenu.for_menu(str) }
|
496
|
-
|
497
|
-
unless file_names[:data]&.count.positive? || files_in_directories&.count.positive? || found_in_block_names[:data]&.count.positive?
|
498
|
-
return { exit: true }
|
499
|
-
end
|
500
|
-
|
501
|
-
vbn = found_in_block_names[:matched_contents].map do |matched_contents|
|
502
|
-
filename, details, = matched_contents
|
503
|
-
nexo = AnsiFormatter.new(@options).format_and_highlight_array(
|
504
|
-
details,
|
505
|
-
highlight: [value]
|
506
|
-
)
|
507
|
-
[FileInMenu.for_menu(filename)] +
|
508
|
-
nexo.map do |str|
|
509
|
-
{ disabled: '', name: (' ' * 20) + str }
|
510
|
-
end
|
511
|
-
end.flatten
|
520
|
+
end
|
512
521
|
|
513
|
-
|
514
|
-
|
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
|
+
)
|
515
530
|
|
516
531
|
@options[:filename] = FileInMenu.from_menu(
|
517
532
|
select_document_if_multiple(
|
518
533
|
choices,
|
519
|
-
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
|
520
536
|
)
|
521
537
|
)
|
522
|
-
|
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
|
523
606
|
end
|
524
607
|
|
525
608
|
## Sets up the options and returns the parsed arguments
|
526
609
|
#
|
527
|
-
def
|
528
|
-
# @options = base_options
|
610
|
+
def initialize_parse_execute_cli
|
529
611
|
@options = HashDelegator.new(base_options)
|
530
|
-
|
612
|
+
@options.p_all_arguments = ARGV.dup # Duplicate ARGV to track original order
|
613
|
+
### should not include after '--'
|
531
614
|
read_configuration_file!(@options,
|
532
615
|
".#{MarkdownExec::APP_NAME.downcase}.yml")
|
533
616
|
|
617
|
+
options_parsed = []
|
534
618
|
@option_parser = OptionParser.new do |opts|
|
535
619
|
executable_name = File.basename($PROGRAM_NAME)
|
536
620
|
opts.banner = [
|
537
621
|
"#{MarkdownExec::APP_NAME}" \
|
538
622
|
" - #{MarkdownExec::APP_DESC} (#{MarkdownExec::VERSION})",
|
539
|
-
"Usage: #{executable_name}
|
623
|
+
"Usage: #{executable_name}" \
|
624
|
+
' [(directory | file [block_name] | search_keyword)] [options]'
|
540
625
|
].join("\n")
|
541
626
|
|
542
627
|
menu_iter do |item|
|
543
|
-
opts_menu_option_append
|
628
|
+
opts_menu_option_append(opts, @options, item, options_parsed)
|
544
629
|
end
|
545
630
|
end
|
546
631
|
@option_parser.load
|
547
632
|
@option_parser.environment
|
548
|
-
@
|
549
|
-
@
|
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
|
+
|
550
642
|
@options.merge(@options.run_state.to_h)
|
643
|
+
end
|
551
644
|
|
552
|
-
|
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
|
553
669
|
end
|
554
670
|
|
555
671
|
##
|
@@ -565,8 +681,9 @@ module MarkdownExec
|
|
565
681
|
->(_) { exit }
|
566
682
|
when 'find', 'open'
|
567
683
|
->(value) {
|
568
|
-
exit if find_value(
|
569
|
-
|
684
|
+
exit if find_value(
|
685
|
+
value, execute_chosen_found: procname == 'open'
|
686
|
+
).fetch(:exit, false)
|
570
687
|
}
|
571
688
|
when 'help'
|
572
689
|
->(_) {
|
@@ -595,6 +712,8 @@ module MarkdownExec
|
|
595
712
|
lambda(&:to_i)
|
596
713
|
when 'val_as_str'
|
597
714
|
lambda(&:to_s)
|
715
|
+
when 'val_as_sym'
|
716
|
+
lambda(&:to_sym)
|
598
717
|
when 'version'
|
599
718
|
lambda { |_|
|
600
719
|
@fout.fout MarkdownExec::VERSION
|
@@ -605,7 +724,17 @@ module MarkdownExec
|
|
605
724
|
end
|
606
725
|
end
|
607
726
|
|
608
|
-
|
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
|
609
738
|
|
610
739
|
def list_default_env
|
611
740
|
menu_iter do |item|
|
@@ -646,6 +775,35 @@ module MarkdownExec
|
|
646
775
|
@options[:md_filename_glob]))
|
647
776
|
end
|
648
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
|
+
|
649
807
|
private
|
650
808
|
|
651
809
|
##
|
@@ -675,37 +833,88 @@ module MarkdownExec
|
|
675
833
|
end.to_yaml
|
676
834
|
end
|
677
835
|
|
678
|
-
def opts_menu_option_append(opts, options, item)
|
836
|
+
def opts_menu_option_append(opts, options, item, options_parsed)
|
679
837
|
return unless item[:long_name].present? || item[:short_name].present?
|
680
838
|
|
681
|
-
|
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
|
+
|
682
851
|
# - long name
|
683
852
|
if item[:long_name].present?
|
684
|
-
"--#{item[:long_name]}
|
853
|
+
optname = "--#{item[:long_name]}"
|
854
|
+
"--#{item[:long_name]}" \
|
855
|
+
"#{item[:arg_name].present? ? " #{item[:arg_name]}" : ''}"
|
685
856
|
end,
|
686
857
|
|
687
858
|
# - short name
|
688
|
-
item[:short_name].present?
|
859
|
+
if item[:short_name].present?
|
860
|
+
"-#{item[:short_name]}" \
|
861
|
+
"#{item[:arg_name].present? ? " #{item[:arg_name]}" : ''}"
|
862
|
+
end,
|
689
863
|
|
690
864
|
# - description and default
|
691
|
-
[
|
692
|
-
|
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.
|
693
898
|
|
694
899
|
# apply proccode, if present, to value
|
695
900
|
# save value to options hash if option is named
|
696
901
|
#
|
697
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)
|
698
905
|
(item[:proccode] ? item[:proccode].call(value) : value).tap do |converted|
|
699
906
|
options[item[:opt_name]] = converted if item[:opt_name]
|
700
907
|
end
|
701
908
|
}
|
702
|
-
].compact
|
909
|
+
].compact
|
910
|
+
opts.on(*switches)
|
703
911
|
end
|
704
912
|
|
705
913
|
def opts_prepare_file_list(options)
|
706
914
|
list_files_specified(
|
707
915
|
determine_filename(
|
708
|
-
specified_filename:
|
916
|
+
specified_filename:
|
917
|
+
options[:filename]&.present? ? options[:filename] : nil,
|
709
918
|
specified_folder: options[:path],
|
710
919
|
default_filename: 'README.md',
|
711
920
|
default_folder: '.'
|
@@ -724,12 +933,21 @@ module MarkdownExec
|
|
724
933
|
public
|
725
934
|
|
726
935
|
def run
|
727
|
-
|
936
|
+
initialize_parse_execute_cli
|
728
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
|
729
944
|
rescue StandardError
|
730
945
|
error_handler('run')
|
731
|
-
|
732
|
-
|
946
|
+
# rubocop:disable Style/RescueStandardError
|
947
|
+
rescue
|
948
|
+
warn 'Exiting...' if $DEBUG
|
949
|
+
exit 1
|
950
|
+
# rubocop:enable Style/RescueStandardError
|
733
951
|
end
|
734
952
|
|
735
953
|
private
|
@@ -741,7 +959,7 @@ module MarkdownExec
|
|
741
959
|
|
742
960
|
saved_name_split filename
|
743
961
|
@options[:save_executed_script] = false
|
744
|
-
@options.
|
962
|
+
@options.vux_main_loop
|
745
963
|
rescue StandardError
|
746
964
|
error_handler('run_last_script')
|
747
965
|
end
|
@@ -763,8 +981,11 @@ module MarkdownExec
|
|
763
981
|
|
764
982
|
opts = options.dup
|
765
983
|
select_option_or_exit(
|
766
|
-
HashDelegator.new(@options)
|
767
|
-
|
984
|
+
HashDelegator.new(@options)
|
985
|
+
.string_send_color(
|
986
|
+
prompt,
|
987
|
+
:prompt_color_after_script_execution
|
988
|
+
),
|
768
989
|
files,
|
769
990
|
opts.merge(per_page: opts[:select_page_height])
|
770
991
|
)
|
@@ -777,6 +998,33 @@ module MarkdownExec
|
|
777
998
|
)&.fetch(:selected)
|
778
999
|
end
|
779
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
|
+
|
780
1028
|
public
|
781
1029
|
|
782
1030
|
def tab_completions(data = menu_for_optparse)
|