markdown_exec 2.3.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 +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)
|