markdown_exec 1.4.1 → 1.5
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 +3 -1
- data/CHANGELOG.md +12 -0
- data/Gemfile.lock +1 -1
- data/bin/bmde +103 -1
- data/bin/tab_completion.sh +2 -2
- data/examples/opts.md +16 -4
- data/lib/block_types.rb +9 -5
- data/lib/filter.rb +15 -4
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/markdown_exec.rb +596 -341
- data/lib/mdoc.rb +106 -32
- data/lib/menu.src.yml +173 -6
- data/lib/menu.yml +62 -7
- metadata +4 -4
data/lib/markdown_exec.rb
CHANGED
@@ -38,10 +38,6 @@ $stdout.sync = true
|
|
38
38
|
|
39
39
|
MDE_HISTORY_ENV_NAME = 'MDE_MENU_HISTORY'
|
40
40
|
|
41
|
-
# macros
|
42
|
-
#
|
43
|
-
LOAD_FILE = true
|
44
|
-
|
45
41
|
# custom error: file specified is missing
|
46
42
|
#
|
47
43
|
class FileMissingError < StandardError; end
|
@@ -63,6 +59,17 @@ class Hash
|
|
63
59
|
end
|
64
60
|
end
|
65
61
|
|
62
|
+
class LoadFile
|
63
|
+
Load = true
|
64
|
+
Reuse = false
|
65
|
+
end
|
66
|
+
|
67
|
+
class MenuState
|
68
|
+
BACK = :back
|
69
|
+
CONTINUE = :continue
|
70
|
+
EXIT = :exit
|
71
|
+
end
|
72
|
+
|
66
73
|
# integer value for comparison
|
67
74
|
#
|
68
75
|
def options_fetch_display_level(options)
|
@@ -110,16 +117,23 @@ def dp(str)
|
|
110
117
|
lout " => #{str}", level: DISPLAY_LEVEL_DEBUG
|
111
118
|
end
|
112
119
|
|
120
|
+
def rpry
|
121
|
+
require 'pry-nav'
|
122
|
+
require 'pry-stack_explorer'
|
123
|
+
end
|
124
|
+
|
113
125
|
public
|
114
126
|
|
115
127
|
# :reek:UtilityFunction
|
116
|
-
def list_recent_output(saved_stdout_folder, saved_stdout_glob,
|
128
|
+
def list_recent_output(saved_stdout_folder, saved_stdout_glob,
|
129
|
+
list_count)
|
117
130
|
SavedFilesMatcher.most_recent_list(saved_stdout_folder,
|
118
131
|
saved_stdout_glob, list_count)
|
119
132
|
end
|
120
133
|
|
121
134
|
# :reek:UtilityFunction
|
122
|
-
def list_recent_scripts(saved_script_folder, saved_script_glob,
|
135
|
+
def list_recent_scripts(saved_script_folder, saved_script_glob,
|
136
|
+
list_count)
|
123
137
|
SavedFilesMatcher.most_recent_list(saved_script_folder,
|
124
138
|
saved_script_glob, list_count)
|
125
139
|
end
|
@@ -174,10 +188,10 @@ module MarkdownExec
|
|
174
188
|
FNR12 = ',~'
|
175
189
|
|
176
190
|
SHELL_COLOR_OPTIONS = {
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
191
|
+
BlockType::BASH => :menu_bash_color,
|
192
|
+
BlockType::LINK => :menu_link_color,
|
193
|
+
BlockType::OPTS => :menu_opts_color,
|
194
|
+
BlockType::VARS => :menu_vars_color
|
181
195
|
}.freeze
|
182
196
|
|
183
197
|
##
|
@@ -209,6 +223,22 @@ module MarkdownExec
|
|
209
223
|
@prompt = tty_prompt_without_disabled_symbol
|
210
224
|
end
|
211
225
|
|
226
|
+
# Adds Back and Exit options to the CLI menu
|
227
|
+
#
|
228
|
+
# @param blocks_in_file [Array] The current blocks in the menu
|
229
|
+
def add_menu_chrome_blocks!(blocks_in_file)
|
230
|
+
return unless @options[:menu_link_format].present?
|
231
|
+
|
232
|
+
if @options[:menu_with_back] && history_state_exist?
|
233
|
+
append_chrome_block(blocks_in_file, MenuState::BACK)
|
234
|
+
end
|
235
|
+
if @options[:menu_with_exit]
|
236
|
+
append_chrome_block(blocks_in_file, MenuState::EXIT)
|
237
|
+
end
|
238
|
+
append_divider(blocks_in_file, @options, :initial)
|
239
|
+
append_divider(blocks_in_file, @options, :final)
|
240
|
+
end
|
241
|
+
|
212
242
|
##
|
213
243
|
# Appends a summary of a block (FCB) to the blocks array.
|
214
244
|
#
|
@@ -218,38 +248,58 @@ module MarkdownExec
|
|
218
248
|
blocks.push get_block_summary(opts, fcb)
|
219
249
|
end
|
220
250
|
|
221
|
-
|
222
|
-
# Appends a final divider to the blocks array if it is specified in options.
|
251
|
+
# Appends a chrome block, which is a menu option for Back or Exit
|
223
252
|
#
|
224
|
-
|
225
|
-
|
253
|
+
# @param blocks_in_file [Array] The current blocks in the menu
|
254
|
+
# @param type [Symbol] The type of chrome block to add (:back or :exit)
|
255
|
+
def append_chrome_block(blocks_in_file, type)
|
256
|
+
case type
|
257
|
+
when MenuState::BACK
|
258
|
+
state = history_state_partition(@options)
|
259
|
+
@hs_curr = state[:unit]
|
260
|
+
@hs_rest = state[:rest]
|
261
|
+
option_name = @options[:menu_option_back_name]
|
262
|
+
insert_at_top = @options[:menu_back_at_top]
|
263
|
+
when MenuState::EXIT
|
264
|
+
option_name = @options[:menu_option_exit_name]
|
265
|
+
insert_at_top = @options[:menu_exit_at_top]
|
266
|
+
end
|
226
267
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
oname: opts[:menu_final_divider] }
|
268
|
+
formatted_name = format(@options[:menu_link_format],
|
269
|
+
safeval(option_name))
|
270
|
+
chrome_block = FCB.new(
|
271
|
+
chrome: true,
|
272
|
+
dname: formatted_name.send(@options[:menu_link_color].to_sym),
|
273
|
+
oname: formatted_name
|
234
274
|
)
|
275
|
+
|
276
|
+
if insert_at_top
|
277
|
+
blocks_in_file.unshift(chrome_block)
|
278
|
+
else
|
279
|
+
blocks_in_file.push(chrome_block)
|
280
|
+
end
|
235
281
|
end
|
236
282
|
|
237
|
-
|
238
|
-
#
|
239
|
-
#
|
240
|
-
|
241
|
-
|
283
|
+
# Appends a divider to the blocks array.
|
284
|
+
# @param blocks [Array] The array of block elements.
|
285
|
+
# @param opts [Hash] Options containing divider configuration.
|
286
|
+
# @param position [Symbol] :initial or :final divider position.
|
287
|
+
def append_divider(blocks, opts, position)
|
288
|
+
divider_key = position == :initial ? :menu_initial_divider : :menu_final_divider
|
289
|
+
unless opts[:menu_divider_format].present? && opts[divider_key].present?
|
290
|
+
return
|
291
|
+
end
|
292
|
+
|
293
|
+
oname = format(opts[:menu_divider_format],
|
294
|
+
safeval(opts[divider_key]))
|
295
|
+
divider = FCB.new(
|
296
|
+
chrome: true,
|
297
|
+
disabled: '',
|
298
|
+
dname: oname.send(opts[:menu_divider_color].to_sym),
|
299
|
+
oname: oname
|
300
|
+
)
|
242
301
|
|
243
|
-
blocks.
|
244
|
-
# name: '',
|
245
|
-
chrome: true,
|
246
|
-
dname: format(
|
247
|
-
opts[:menu_divider_format],
|
248
|
-
opts[:menu_initial_divider]
|
249
|
-
).send(opts[:menu_divider_color].to_sym),
|
250
|
-
oname: opts[:menu_initial_divider],
|
251
|
-
disabled: '' # __LINE__.to_s
|
252
|
-
})
|
302
|
+
position == :initial ? blocks.unshift(divider) : blocks.push(divider)
|
253
303
|
end
|
254
304
|
|
255
305
|
# Execute a code block after approval and provide user interaction options.
|
@@ -261,14 +311,12 @@ module MarkdownExec
|
|
261
311
|
# @param opts [Hash] Options hash containing configuration settings.
|
262
312
|
# @param mdoc [YourMDocClass] An instance of the MDoc class.
|
263
313
|
#
|
264
|
-
def approve_and_execute_block(opts, mdoc)
|
265
|
-
selected
|
266
|
-
|
267
|
-
if selected.fetch(:shell, '') == BLOCK_TYPE_LINK
|
314
|
+
def approve_and_execute_block(selected, opts, mdoc)
|
315
|
+
if selected.fetch(:shell, '') == BlockType::LINK
|
268
316
|
handle_shell_link(opts, selected.fetch(:body, ''), mdoc)
|
269
|
-
elsif opts.fetch(:
|
317
|
+
elsif opts.fetch(:s_back, false)
|
270
318
|
handle_back_link(opts)
|
271
|
-
elsif selected[:shell] ==
|
319
|
+
elsif selected[:shell] == BlockType::OPTS
|
272
320
|
handle_shell_opts(opts, selected)
|
273
321
|
else
|
274
322
|
handle_remainder_blocks(mdoc, opts, selected)
|
@@ -302,10 +350,23 @@ module MarkdownExec
|
|
302
350
|
env_str(item[:env_var],
|
303
351
|
default: OptionValue.for_hash(item_default))
|
304
352
|
end
|
305
|
-
[item[:opt_name],
|
353
|
+
[item[:opt_name],
|
354
|
+
item[:proccode] ? item[:proccode].call(value) : value]
|
306
355
|
end.compact.to_h
|
307
356
|
end
|
308
357
|
|
358
|
+
# Finds the first hash-like element within an enumerable collection where the specified key
|
359
|
+
# matches the given value. Returns a default value if no match is found.
|
360
|
+
#
|
361
|
+
# @param blocks [Enumerable] An enumerable collection of hash-like objects.
|
362
|
+
# @param key [Object] The key to look up in each hash-like object.
|
363
|
+
# @param value [Object] The value to compare against the value associated with the key.
|
364
|
+
# @param default [Object] The default value to return if no match is found (optional).
|
365
|
+
# @return [Object, nil] The found hash-like object, or the default value if no match is found.
|
366
|
+
def block_find(blocks, key, value, default = nil)
|
367
|
+
blocks.find { |item| item[key] == value } || default
|
368
|
+
end
|
369
|
+
|
309
370
|
def blocks_per_opts(blocks, opts)
|
310
371
|
return blocks if opts[:struct]
|
311
372
|
|
@@ -347,7 +408,7 @@ module MarkdownExec
|
|
347
408
|
# @return [Array<String>] Required code blocks as an array of lines.
|
348
409
|
def collect_required_code_lines(mdoc, selected, opts: {})
|
349
410
|
# Apply hash in opts block to environment variables
|
350
|
-
if selected[:shell] ==
|
411
|
+
if selected[:shell] == BlockType::VARS
|
351
412
|
data = YAML.load(selected[:body].join("\n"))
|
352
413
|
data.each_key do |key|
|
353
414
|
ENV[key] = value = data[key].to_s
|
@@ -361,7 +422,8 @@ module MarkdownExec
|
|
361
422
|
end
|
362
423
|
end
|
363
424
|
|
364
|
-
required = mdoc.collect_recursively_required_code(opts[:block_name],
|
425
|
+
required = mdoc.collect_recursively_required_code(opts[:block_name],
|
426
|
+
opts: opts)
|
365
427
|
read_required_blocks_from_temp_file + required[:code]
|
366
428
|
end
|
367
429
|
|
@@ -417,6 +479,20 @@ module MarkdownExec
|
|
417
479
|
fout "Error ENOENT: #{err.inspect}"
|
418
480
|
end
|
419
481
|
|
482
|
+
def command_or_user_selected_block(blocks_in_file, blocks_menu, default,
|
483
|
+
opts)
|
484
|
+
if opts[:block_name].present?
|
485
|
+
block = blocks_in_file.find do |item|
|
486
|
+
item[:oname] == opts[:block_name]
|
487
|
+
end
|
488
|
+
else
|
489
|
+
block, state = wait_for_user_selected_block(blocks_in_file, blocks_menu, default,
|
490
|
+
opts)
|
491
|
+
end
|
492
|
+
|
493
|
+
[block, state]
|
494
|
+
end
|
495
|
+
|
420
496
|
def copy_to_clipboard(required_lines)
|
421
497
|
text = required_lines.flatten.join($INPUT_RECORD_SEPARATOR)
|
422
498
|
Clipboard.copy(text)
|
@@ -434,7 +510,48 @@ module MarkdownExec
|
|
434
510
|
cnt / 2
|
435
511
|
end
|
436
512
|
|
437
|
-
|
513
|
+
##
|
514
|
+
# Creates and adds a formatted block to the blocks array based on the provided match and format options.
|
515
|
+
# @param blocks [Array] The array of blocks to add the new block to.
|
516
|
+
# @param fcb [FCB] The file control block containing the line to match against.
|
517
|
+
# @param match_data [MatchData] The match data containing named captures for formatting.
|
518
|
+
# @param format_option [String] The format string to be used for the new block.
|
519
|
+
# @param color_method [Symbol] The color method to apply to the block's display name.
|
520
|
+
def create_and_add_chrome_block(blocks, _fcb, match_data, format_option,
|
521
|
+
color_method)
|
522
|
+
oname = format(format_option,
|
523
|
+
match_data.named_captures.transform_keys(&:to_sym))
|
524
|
+
blocks.push FCB.new(
|
525
|
+
chrome: true,
|
526
|
+
disabled: '',
|
527
|
+
dname: oname.send(color_method),
|
528
|
+
oname: oname
|
529
|
+
)
|
530
|
+
end
|
531
|
+
|
532
|
+
##
|
533
|
+
# Processes lines within the file and converts them into blocks if they match certain criteria.
|
534
|
+
# @param blocks [Array] The array to append new blocks to.
|
535
|
+
# @param fcb [FCB] The file control block being processed.
|
536
|
+
# @param opts [Hash] Options containing configuration for line processing.
|
537
|
+
# @param use_chrome [Boolean] Indicates if the chrome styling should be applied.
|
538
|
+
def create_and_add_chrome_blocks(blocks, fcb, opts, use_chrome)
|
539
|
+
return unless use_chrome
|
540
|
+
|
541
|
+
if opts[:menu_note_match].present? && (mbody = fcb.body[0].match opts[:menu_note_match])
|
542
|
+
create_and_add_chrome_block(blocks, fcb, mbody, opts[:menu_note_format],
|
543
|
+
opts[:menu_note_color].to_sym)
|
544
|
+
elsif opts[:menu_divider_match].present? && (mbody = fcb.body[0].match opts[:menu_divider_match])
|
545
|
+
create_and_add_chrome_block(blocks, fcb, mbody, opts[:menu_divider_format],
|
546
|
+
opts[:menu_divider_color].to_sym)
|
547
|
+
elsif opts[:menu_task_match].present? && (mbody = fcb.body[0].match opts[:menu_task_match])
|
548
|
+
create_and_add_chrome_block(blocks, fcb, mbody, opts[:menu_task_format],
|
549
|
+
opts[:menu_task_color].to_sym)
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
def create_and_write_file_with_permissions(file_path, content,
|
554
|
+
chmod_value)
|
438
555
|
dirname = File.dirname(file_path)
|
439
556
|
FileUtils.mkdir_p dirname
|
440
557
|
File.write(file_path, content)
|
@@ -450,13 +567,29 @@ module MarkdownExec
|
|
450
567
|
def delete_required_temp_file
|
451
568
|
temp_blocks_file_path = ENV.fetch('MDE_LINK_REQUIRED_FILE', nil)
|
452
569
|
|
453
|
-
|
570
|
+
if temp_blocks_file_path.nil? || temp_blocks_file_path.empty?
|
571
|
+
return
|
572
|
+
end
|
454
573
|
|
455
574
|
FileUtils.rm_f(temp_blocks_file_path)
|
456
575
|
|
457
576
|
clear_required_file
|
458
577
|
end
|
459
578
|
|
579
|
+
# Derives a title from the body of an FCB object.
|
580
|
+
# @param fcb [Object] The FCB object whose title is to be derived.
|
581
|
+
# @return [String] The derived title.
|
582
|
+
def derive_title_from_body(fcb)
|
583
|
+
body_content = fcb&.body
|
584
|
+
return '' unless body_content
|
585
|
+
|
586
|
+
if body_content.count == 1
|
587
|
+
body_content.first
|
588
|
+
else
|
589
|
+
format_multiline_body_as_title(body_content)
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
460
593
|
## Determines the correct filename to use for searching files
|
461
594
|
#
|
462
595
|
def determine_filename(specified_filename: nil, specified_folder: nil, default_filename: nil,
|
@@ -464,7 +597,8 @@ module MarkdownExec
|
|
464
597
|
if specified_filename&.present?
|
465
598
|
return specified_filename if specified_filename.start_with?('/')
|
466
599
|
|
467
|
-
File.join(specified_folder || default_folder,
|
600
|
+
File.join(specified_folder || default_folder,
|
601
|
+
specified_filename)
|
468
602
|
elsif specified_folder&.present?
|
469
603
|
File.join(specified_folder,
|
470
604
|
filetree ? @options[:md_filename_match] : @options[:md_filename_glob])
|
@@ -486,7 +620,7 @@ module MarkdownExec
|
|
486
620
|
command_execute(
|
487
621
|
opts,
|
488
622
|
required_lines.flatten.join("\n"),
|
489
|
-
args: opts.fetch(:
|
623
|
+
args: opts.fetch(:s_pass_args, [])
|
490
624
|
)
|
491
625
|
initialize_and_save_execution_output
|
492
626
|
output_execution_summary
|
@@ -496,20 +630,22 @@ module MarkdownExec
|
|
496
630
|
# Reports and executes block logic
|
497
631
|
def execute_block_logic(files)
|
498
632
|
@options[:filename] = select_document_if_multiple(files)
|
499
|
-
select_approve_and_execute_block({
|
500
|
-
|
501
|
-
struct: true
|
502
|
-
})
|
633
|
+
select_approve_and_execute_block({ bash: true,
|
634
|
+
struct: true })
|
503
635
|
end
|
504
636
|
|
505
637
|
## Executes the block specified in the options
|
506
638
|
#
|
507
639
|
def execute_block_with_error_handling(rest)
|
508
640
|
finalize_cli_argument_processing(rest)
|
509
|
-
@options[:
|
641
|
+
@options[:s_cli_rest] = rest
|
510
642
|
execute_code_block_based_on_options(@options)
|
511
643
|
rescue FileMissingError => err
|
512
644
|
puts "File missing: #{err}"
|
645
|
+
rescue StandardError => err
|
646
|
+
warn(error = "ERROR ** MarkParse.execute_block_with_error_handling(); #{err.inspect}")
|
647
|
+
binding.pry if $tap_enable
|
648
|
+
raise ArgumentError, error
|
513
649
|
end
|
514
650
|
|
515
651
|
# Main method to execute a block based on options and block_name
|
@@ -521,7 +657,8 @@ module MarkdownExec
|
|
521
657
|
doc_glob: -> { fout options[:md_filename_glob] },
|
522
658
|
list_blocks: lambda do
|
523
659
|
fout_list (files.map do |file|
|
524
|
-
|
660
|
+
menu_with_block_labels(filename: file,
|
661
|
+
struct: true)
|
525
662
|
end).flatten(1)
|
526
663
|
end,
|
527
664
|
list_default_yaml: -> { fout_list list_default_yaml },
|
@@ -571,15 +708,6 @@ module MarkdownExec
|
|
571
708
|
false
|
572
709
|
end
|
573
710
|
|
574
|
-
##
|
575
|
-
# Determines the types of blocks to select based on the filter.
|
576
|
-
#
|
577
|
-
def filter_block_types
|
578
|
-
## return type of blocks to select
|
579
|
-
#
|
580
|
-
%i[blocks line]
|
581
|
-
end
|
582
|
-
|
583
711
|
## post-parse options configuration
|
584
712
|
#
|
585
713
|
def finalize_cli_argument_processing(rest)
|
@@ -601,6 +729,15 @@ module MarkdownExec
|
|
601
729
|
@options[:block_name] = block_name if block_name.present?
|
602
730
|
end
|
603
731
|
|
732
|
+
# Formats multiline body content as a title string.
|
733
|
+
# @param body_lines [Array<String>] The lines of body content.
|
734
|
+
# @return [String] Formatted title.
|
735
|
+
def format_multiline_body_as_title(body_lines)
|
736
|
+
body_lines.map.with_index do |line, index|
|
737
|
+
index.zero? ? line : " #{line}"
|
738
|
+
end.join("\n") << "\n"
|
739
|
+
end
|
740
|
+
|
604
741
|
## summarize blocks
|
605
742
|
#
|
606
743
|
def get_block_summary(call_options, fcb)
|
@@ -614,9 +751,12 @@ module MarkdownExec
|
|
614
751
|
else
|
615
752
|
fcb.title
|
616
753
|
end
|
617
|
-
bm = extract_named_captures_from_option(titlexcall,
|
618
|
-
|
619
|
-
fcb.
|
754
|
+
bm = extract_named_captures_from_option(titlexcall,
|
755
|
+
opts[:block_name_match])
|
756
|
+
fcb.stdin = extract_named_captures_from_option(titlexcall,
|
757
|
+
opts[:block_stdin_scan])
|
758
|
+
fcb.stdout = extract_named_captures_from_option(titlexcall,
|
759
|
+
opts[:block_stdout_scan])
|
620
760
|
|
621
761
|
shell_color_option = SHELL_COLOR_OPTIONS[fcb[:shell]]
|
622
762
|
fcb.title = fcb.oname = bm && bm[1] ? bm[:title] : titlexcall
|
@@ -628,22 +768,13 @@ module MarkdownExec
|
|
628
768
|
fcb
|
629
769
|
end
|
630
770
|
|
631
|
-
##
|
632
|
-
# Handles errors that occur during the block listing process.
|
633
|
-
#
|
634
|
-
def handle_error(err)
|
635
|
-
warn(error = "ERROR ** MarkParse.list_blocks_in_file(); #{err.inspect}")
|
636
|
-
warn(caller[0..4])
|
637
|
-
raise StandardError, error
|
638
|
-
end
|
639
|
-
|
640
771
|
# Handles the link-back operation.
|
641
772
|
#
|
642
773
|
# @param opts [Hash] Configuration options hash.
|
643
|
-
# @return [Array<Symbol, String>] A tuple containing a
|
774
|
+
# @return [Array<Symbol, String>] A tuple containing a LoadFile flag and an empty string.
|
644
775
|
def handle_back_link(opts)
|
645
776
|
history_state_pop(opts)
|
646
|
-
[
|
777
|
+
[LoadFile::Load, '']
|
647
778
|
end
|
648
779
|
|
649
780
|
# Handles the execution and display of remainder blocks from a selected menu item.
|
@@ -651,18 +782,27 @@ module MarkdownExec
|
|
651
782
|
# @param mdoc [Object] Document object containing code blocks.
|
652
783
|
# @param opts [Hash] Configuration options hash.
|
653
784
|
# @param selected [Hash] Selected item from the menu.
|
654
|
-
# @return [Array<Symbol, String>] A tuple containing a
|
785
|
+
# @return [Array<Symbol, String>] A tuple containing a LoadFile flag and an empty string.
|
655
786
|
# @note The function can prompt the user for approval before executing code if opts[:user_must_approve] is true.
|
656
787
|
def handle_remainder_blocks(mdoc, opts, selected)
|
657
|
-
required_lines = collect_required_code_lines(mdoc, selected,
|
788
|
+
required_lines = collect_required_code_lines(mdoc, selected,
|
789
|
+
opts: opts)
|
658
790
|
if opts[:output_script] || opts[:user_must_approve]
|
659
791
|
display_required_code(opts, required_lines)
|
660
792
|
end
|
661
|
-
allow = opts[:user_must_approve]
|
662
|
-
|
663
|
-
|
793
|
+
allow = if opts[:user_must_approve]
|
794
|
+
prompt_for_user_approval(opts,
|
795
|
+
required_lines)
|
796
|
+
else
|
797
|
+
true
|
798
|
+
end
|
799
|
+
opts[:s_ir_approve] = allow
|
800
|
+
if opts[:s_ir_approve]
|
801
|
+
execute_approved_block(opts,
|
802
|
+
required_lines)
|
803
|
+
end
|
664
804
|
|
665
|
-
[
|
805
|
+
[LoadFile::Reuse, '']
|
666
806
|
end
|
667
807
|
|
668
808
|
# Handles the link-shell operation.
|
@@ -670,11 +810,11 @@ module MarkdownExec
|
|
670
810
|
# @param opts [Hash] Configuration options hash.
|
671
811
|
# @param body [Array<String>] The body content.
|
672
812
|
# @param mdoc [Object] Document object containing code blocks.
|
673
|
-
# @return [Array<Symbol, String>] A tuple containing a
|
813
|
+
# @return [Array<Symbol, String>] A tuple containing a LoadFile flag and a block name.
|
674
814
|
def handle_shell_link(opts, body, mdoc)
|
675
815
|
data = body.present? ? YAML.load(body.join("\n")) : {}
|
676
816
|
data_file = data.fetch('file', nil)
|
677
|
-
return [
|
817
|
+
return [LoadFile::Reuse, ''] unless data_file
|
678
818
|
|
679
819
|
history_state_push(mdoc, data_file, opts)
|
680
820
|
|
@@ -682,18 +822,19 @@ module MarkdownExec
|
|
682
822
|
ENV[var[0]] = var[1].to_s
|
683
823
|
end
|
684
824
|
|
685
|
-
[
|
825
|
+
[LoadFile::Load, data.fetch('block', '')]
|
686
826
|
end
|
687
827
|
|
688
828
|
# Handles options for the shell.
|
689
829
|
#
|
690
830
|
# @param opts [Hash] Configuration options hash.
|
691
831
|
# @param selected [Hash] Selected item from the menu.
|
692
|
-
# @return [Array<Symbol, String>] A tuple containing a
|
693
|
-
def handle_shell_opts(opts, selected)
|
832
|
+
# @return [Array<Symbol, String>] A tuple containing a LoadFile::Reuse flag and an empty string.
|
833
|
+
def handle_shell_opts(opts, selected, tgt2 = nil)
|
694
834
|
data = YAML.load(selected[:body].join("\n"))
|
695
835
|
data.each_key do |key|
|
696
|
-
opts[key.to_sym] = value = data[key]
|
836
|
+
opts[key.to_sym] = value = data[key]
|
837
|
+
tgt2[key.to_sym] = value if tgt2
|
697
838
|
next unless opts[:menu_opts_set_format].present?
|
698
839
|
|
699
840
|
print format(
|
@@ -702,7 +843,7 @@ module MarkdownExec
|
|
702
843
|
value: value }
|
703
844
|
).send(opts[:menu_opts_set_color].to_sym)
|
704
845
|
end
|
705
|
-
[
|
846
|
+
[LoadFile::Reuse, '']
|
706
847
|
end
|
707
848
|
|
708
849
|
# Handles reading and processing lines from a given IO stream
|
@@ -712,7 +853,8 @@ module MarkdownExec
|
|
712
853
|
def handle_stream(opts, stream, file_type, swap: false)
|
713
854
|
Thread.new do
|
714
855
|
until (line = stream.gets).nil?
|
715
|
-
@execute_files[file_type] =
|
856
|
+
@execute_files[file_type] =
|
857
|
+
@execute_files[file_type] + [line.strip]
|
716
858
|
print line if opts[:output_stdout]
|
717
859
|
yield line if block_given?
|
718
860
|
end
|
@@ -755,7 +897,8 @@ module MarkdownExec
|
|
755
897
|
#
|
756
898
|
def initialize_and_parse_cli_options
|
757
899
|
@options = base_options
|
758
|
-
read_configuration_file!(@options,
|
900
|
+
read_configuration_file!(@options,
|
901
|
+
".#{MarkdownExec::APP_NAME.downcase}.yml")
|
759
902
|
|
760
903
|
@option_parser = OptionParser.new do |opts|
|
761
904
|
executable_name = File.basename($PROGRAM_NAME)
|
@@ -773,7 +916,7 @@ module MarkdownExec
|
|
773
916
|
@option_parser.environment
|
774
917
|
|
775
918
|
rest = @option_parser.parse!(arguments_for_mde)
|
776
|
-
@options[:
|
919
|
+
@options[:s_pass_args] = ARGV[rest.count + 1..]
|
777
920
|
|
778
921
|
rest
|
779
922
|
end
|
@@ -783,7 +926,8 @@ module MarkdownExec
|
|
783
926
|
|
784
927
|
@options[:logged_stdout_filename] =
|
785
928
|
SavedAsset.stdout_name(blockname: @options[:block_name],
|
786
|
-
filename: File.basename(@options[:filename],
|
929
|
+
filename: File.basename(@options[:filename],
|
930
|
+
'.*'),
|
787
931
|
prefix: @options[:logged_stdout_filename_prefix],
|
788
932
|
time: Time.now.utc)
|
789
933
|
|
@@ -812,83 +956,56 @@ module MarkdownExec
|
|
812
956
|
|
813
957
|
state = initialize_state(opts)
|
814
958
|
|
815
|
-
# get type of messages to select
|
816
959
|
selected_messages = yield :filter
|
817
960
|
|
818
961
|
cfile.readlines(opts[:filename]).each do |line|
|
819
962
|
next unless line
|
820
963
|
|
821
|
-
update_line_and_block_state(line, state, opts, selected_messages,
|
964
|
+
update_line_and_block_state(line, state, opts, selected_messages,
|
965
|
+
&block)
|
822
966
|
end
|
823
967
|
end
|
824
968
|
|
825
969
|
##
|
826
|
-
# Returns a
|
827
|
-
# The
|
828
|
-
#
|
829
|
-
# @
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
if opts[:menu_divider_match].present? &&
|
864
|
-
(mbody = fcb.body[0].match opts[:menu_divider_match])
|
865
|
-
if use_chrome
|
866
|
-
blocks.push FCB.new(
|
867
|
-
{ chrome: true,
|
868
|
-
disabled: '',
|
869
|
-
dname: format(opts[:menu_divider_format],
|
870
|
-
mbody[:name]).send(opts[:menu_divider_color].to_sym),
|
871
|
-
oname: mbody[:name] }
|
872
|
-
)
|
873
|
-
end
|
874
|
-
elsif opts[:menu_task_match].present? &&
|
875
|
-
(fcb.body[0].match opts[:menu_task_match])
|
876
|
-
if use_chrome
|
877
|
-
blocks.push FCB.new(
|
878
|
-
{ chrome: true,
|
879
|
-
disabled: '',
|
880
|
-
dname: format(
|
881
|
-
opts[:menu_task_format],
|
882
|
-
$~.named_captures.transform_keys(&:to_sym)
|
883
|
-
).send(opts[:menu_task_color].to_sym),
|
884
|
-
oname: format(
|
885
|
-
opts[:menu_task_format],
|
886
|
-
$~.named_captures.transform_keys(&:to_sym)
|
887
|
-
) }
|
888
|
-
)
|
889
|
-
end
|
970
|
+
# Returns a lambda expression based on the given procname.
|
971
|
+
# @param procname [String] The name of the process to generate a lambda for.
|
972
|
+
# @param options [Hash] The options hash, necessary for some lambdas to access.
|
973
|
+
# @return [Lambda] The corresponding lambda expression.
|
974
|
+
def lambda_for_procname(procname, options)
|
975
|
+
case procname
|
976
|
+
when 'debug'
|
977
|
+
lambda { |value|
|
978
|
+
tap_config value: value
|
979
|
+
}
|
980
|
+
when 'exit'
|
981
|
+
->(_) { exit }
|
982
|
+
when 'help'
|
983
|
+
lambda { |_|
|
984
|
+
fout menu_help
|
985
|
+
exit
|
986
|
+
}
|
987
|
+
when 'path'
|
988
|
+
->(value) { read_configuration_file!(options, value) }
|
989
|
+
when 'show_config'
|
990
|
+
lambda { |_|
|
991
|
+
finalize_cli_argument_processing(options)
|
992
|
+
fout options.sort_by_key.to_yaml
|
993
|
+
}
|
994
|
+
when 'val_as_bool'
|
995
|
+
lambda { |value|
|
996
|
+
value.instance_of?(::String) ? (value.chomp != '0') : value
|
997
|
+
}
|
998
|
+
when 'val_as_int'
|
999
|
+
->(value) { value.to_i }
|
1000
|
+
when 'val_as_str'
|
1001
|
+
->(value) { value.to_s }
|
1002
|
+
when 'version'
|
1003
|
+
lambda { |_|
|
1004
|
+
fout MarkdownExec::VERSION
|
1005
|
+
exit
|
1006
|
+
}
|
890
1007
|
else
|
891
|
-
|
1008
|
+
procname
|
892
1009
|
end
|
893
1010
|
end
|
894
1011
|
|
@@ -944,38 +1061,68 @@ module MarkdownExec
|
|
944
1061
|
#
|
945
1062
|
def list_named_blocks_in_file(call_options = {}, &options_block)
|
946
1063
|
opts = optsmerge call_options, options_block
|
1064
|
+
blocks_per_opts(
|
1065
|
+
menu_from_file(opts.merge(struct: true)).select do |fcb|
|
1066
|
+
Filter.fcb_select?(opts.merge(no_chrome: true), fcb)
|
1067
|
+
end, opts
|
1068
|
+
)
|
1069
|
+
end
|
1070
|
+
|
1071
|
+
# return true if values were modified
|
1072
|
+
# execute block once per filename
|
1073
|
+
#
|
1074
|
+
def load_auto_blocks(opts, blocks_in_file)
|
1075
|
+
return unless opts[:document_load_opts_block_name].present?
|
1076
|
+
return if opts[:s_most_recent_filename] == opts[:filename]
|
1077
|
+
|
1078
|
+
block = block_find(blocks_in_file, :oname,
|
1079
|
+
opts[:document_load_opts_block_name])
|
1080
|
+
return unless block
|
1081
|
+
|
1082
|
+
handle_shell_opts(opts, block, @options)
|
1083
|
+
opts[:s_most_recent_filename] = opts[:filename]
|
1084
|
+
true
|
1085
|
+
end
|
947
1086
|
|
948
|
-
|
949
|
-
|
950
|
-
|
1087
|
+
def mdoc_and_menu_from_file(opts)
|
1088
|
+
menu_blocks = menu_from_file(opts.merge(struct: true))
|
1089
|
+
mdoc = MDoc.new(menu_blocks) do |nopts|
|
1090
|
+
opts.merge!(nopts)
|
951
1091
|
end
|
952
|
-
|
1092
|
+
[menu_blocks, mdoc]
|
953
1093
|
end
|
954
1094
|
|
955
1095
|
## Handles the file loading and returns the blocks in the file and MDoc instance
|
956
1096
|
#
|
957
|
-
def
|
958
|
-
blocks_in_file =
|
959
|
-
|
960
|
-
|
1097
|
+
def mdoc_menu_and_selected_from_file(opts)
|
1098
|
+
blocks_in_file, mdoc = mdoc_and_menu_from_file(opts)
|
1099
|
+
if load_auto_blocks(opts, blocks_in_file)
|
1100
|
+
# recreate menu with new options
|
1101
|
+
#
|
1102
|
+
blocks_in_file, mdoc = mdoc_and_menu_from_file(opts)
|
961
1103
|
end
|
1104
|
+
|
962
1105
|
blocks_menu = mdoc.fcbs_per_options(opts.merge(struct: true))
|
1106
|
+
add_menu_chrome_blocks!(blocks_menu)
|
963
1107
|
[blocks_in_file, blocks_menu, mdoc]
|
964
1108
|
end
|
965
1109
|
|
966
|
-
def
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
1110
|
+
def menu_chrome_colored_option(opts,
|
1111
|
+
option_symbol = :menu_option_back_name)
|
1112
|
+
if opts[:menu_chrome_color]
|
1113
|
+
menu_chrome_formatted_option(opts,
|
1114
|
+
option_symbol).send(opts[:menu_chrome_color].to_sym)
|
1115
|
+
else
|
1116
|
+
menu_chrome_formatted_option(opts, option_symbol)
|
1117
|
+
end
|
1118
|
+
end
|
1119
|
+
|
1120
|
+
def menu_chrome_formatted_option(opts,
|
1121
|
+
option_symbol = :menu_option_back_name)
|
1122
|
+
val1 = safeval(opts.fetch(option_symbol, ''))
|
1123
|
+
val1 unless opts[:menu_chrome_format]
|
1124
|
+
|
1125
|
+
format(opts[:menu_chrome_format], val1)
|
979
1126
|
end
|
980
1127
|
|
981
1128
|
def menu_export(data = menu_for_optparse)
|
@@ -995,7 +1142,13 @@ module MarkdownExec
|
|
995
1142
|
when :line
|
996
1143
|
if options[:menu_divider_match] &&
|
997
1144
|
(mbody = fcb.body[0].match(options[:menu_divider_match]))
|
998
|
-
menu.push FCB.new({ dname: mbody[:name], oname: mbody[:name],
|
1145
|
+
menu.push FCB.new({ dname: mbody[:name], oname: mbody[:name],
|
1146
|
+
disabled: '' })
|
1147
|
+
end
|
1148
|
+
if options[:menu_note_match] &&
|
1149
|
+
(mbody = fcb.body[0].match(options[:menu_note_match]))
|
1150
|
+
menu.push FCB.new({ dname: mbody[:name], oname: mbody[:name],
|
1151
|
+
disabled: '' })
|
999
1152
|
end
|
1000
1153
|
when :blocks
|
1001
1154
|
menu += [fcb.oname]
|
@@ -1004,57 +1157,49 @@ module MarkdownExec
|
|
1004
1157
|
menu
|
1005
1158
|
end
|
1006
1159
|
|
1007
|
-
|
1008
|
-
#
|
1160
|
+
##
|
1161
|
+
# Generates a menu suitable for OptionParser from the menu items defined in YAML format.
|
1162
|
+
# @return [Array<Hash>] The array of option hashes for OptionParser.
|
1009
1163
|
def menu_for_optparse
|
1010
1164
|
menu_from_yaml.map do |menu_item|
|
1011
1165
|
menu_item.merge(
|
1012
|
-
|
1013
|
-
|
1014
|
-
proccode: case menu_item[:procname]
|
1015
|
-
when 'debug'
|
1016
|
-
lambda { |value|
|
1017
|
-
tap_config value: value
|
1018
|
-
}
|
1019
|
-
when 'exit'
|
1020
|
-
lambda { |_|
|
1021
|
-
exit
|
1022
|
-
}
|
1023
|
-
when 'help'
|
1024
|
-
lambda { |_|
|
1025
|
-
fout menu_help
|
1026
|
-
exit
|
1027
|
-
}
|
1028
|
-
when 'path'
|
1029
|
-
lambda { |value|
|
1030
|
-
read_configuration_file!(options, value)
|
1031
|
-
}
|
1032
|
-
when 'show_config'
|
1033
|
-
lambda { |_|
|
1034
|
-
finalize_cli_argument_processing(options)
|
1035
|
-
fout options.sort_by_key.to_yaml
|
1036
|
-
}
|
1037
|
-
when 'val_as_bool'
|
1038
|
-
lambda { |value|
|
1039
|
-
value.instance_of?(::String) ? (value.chomp != '0') : value
|
1040
|
-
}
|
1041
|
-
when 'val_as_int'
|
1042
|
-
->(value) { value.to_i }
|
1043
|
-
when 'val_as_str'
|
1044
|
-
->(value) { value.to_s }
|
1045
|
-
when 'version'
|
1046
|
-
lambda { |_|
|
1047
|
-
fout MarkdownExec::VERSION
|
1048
|
-
exit
|
1049
|
-
}
|
1050
|
-
else
|
1051
|
-
menu_item[:procname]
|
1052
|
-
end
|
1053
|
-
}
|
1166
|
+
opt_name: menu_item[:opt_name]&.to_sym,
|
1167
|
+
proccode: lambda_for_procname(menu_item[:procname], options)
|
1054
1168
|
)
|
1055
1169
|
end
|
1056
1170
|
end
|
1057
1171
|
|
1172
|
+
##
|
1173
|
+
# Returns a list of blocks in a given file, including dividers, tasks, and other types of blocks.
|
1174
|
+
# The list can be customized via call_options and options_block.
|
1175
|
+
#
|
1176
|
+
# @param call_options [Hash] Options passed as an argument.
|
1177
|
+
# @param options_block [Proc] Block for dynamic option manipulation.
|
1178
|
+
# @return [Array<FCB>] An array of FCB objects representing the blocks.
|
1179
|
+
#
|
1180
|
+
def menu_from_file(call_options = {},
|
1181
|
+
&options_block)
|
1182
|
+
opts = optsmerge(call_options, options_block)
|
1183
|
+
use_chrome = !opts[:no_chrome]
|
1184
|
+
|
1185
|
+
blocks = []
|
1186
|
+
iter_blocks_in_file(opts) do |btype, fcb|
|
1187
|
+
case btype
|
1188
|
+
when :blocks
|
1189
|
+
append_block_summary(blocks, fcb, opts)
|
1190
|
+
when :filter # what btypes are responded to?
|
1191
|
+
%i[blocks line]
|
1192
|
+
when :line
|
1193
|
+
create_and_add_chrome_blocks(blocks, fcb, opts, use_chrome)
|
1194
|
+
end
|
1195
|
+
end
|
1196
|
+
blocks
|
1197
|
+
rescue StandardError => err
|
1198
|
+
warn(error = "ERROR ** MarkParse.menu_from_file(); #{err.inspect}")
|
1199
|
+
warn(caller[0..4])
|
1200
|
+
raise StandardError, error
|
1201
|
+
end
|
1202
|
+
|
1058
1203
|
def menu_help
|
1059
1204
|
@option_parser.help
|
1060
1205
|
end
|
@@ -1064,7 +1209,9 @@ module MarkdownExec
|
|
1064
1209
|
end
|
1065
1210
|
|
1066
1211
|
def menu_option_append(opts, options, item)
|
1067
|
-
|
1212
|
+
unless item[:long_name].present? || item[:short_name].present?
|
1213
|
+
return
|
1214
|
+
end
|
1068
1215
|
|
1069
1216
|
opts.on(*[
|
1070
1217
|
# - long name
|
@@ -1077,7 +1224,9 @@ module MarkdownExec
|
|
1077
1224
|
|
1078
1225
|
# - description and default
|
1079
1226
|
[item[:description],
|
1080
|
-
(
|
1227
|
+
(if item[:default].present?
|
1228
|
+
"[#{value_for_cli item[:default]}]"
|
1229
|
+
end)].compact.join(' '),
|
1081
1230
|
|
1082
1231
|
# apply proccode, if present, to value
|
1083
1232
|
# save value to options hash if option is named
|
@@ -1090,9 +1239,24 @@ module MarkdownExec
|
|
1090
1239
|
].compact)
|
1091
1240
|
end
|
1092
1241
|
|
1242
|
+
def menu_with_block_labels(call_options = {})
|
1243
|
+
opts = options.merge(call_options)
|
1244
|
+
menu_from_file(opts).map do |fcb|
|
1245
|
+
BlockLabel.make(
|
1246
|
+
filename: opts[:filename],
|
1247
|
+
headings: fcb.fetch(:headings, []),
|
1248
|
+
menu_blocks_with_docname: opts[:menu_blocks_with_docname],
|
1249
|
+
menu_blocks_with_headings: opts[:menu_blocks_with_headings],
|
1250
|
+
title: fcb[:title],
|
1251
|
+
text: fcb[:text],
|
1252
|
+
body: fcb[:body]
|
1253
|
+
)
|
1254
|
+
end.compact
|
1255
|
+
end
|
1256
|
+
|
1093
1257
|
def next_block_name_from_command_line_arguments(opts)
|
1094
|
-
if opts[:
|
1095
|
-
opts[:block_name] = opts[:
|
1258
|
+
if opts[:s_cli_rest].present?
|
1259
|
+
opts[:block_name] = opts[:s_cli_rest].pop
|
1096
1260
|
false # repeat_menu
|
1097
1261
|
else
|
1098
1262
|
true # repeat_menu
|
@@ -1119,7 +1283,10 @@ module MarkdownExec
|
|
1119
1283
|
|
1120
1284
|
[['Script', :saved_filespec],
|
1121
1285
|
['StdOut', :logged_stdout_filespec]].each do |label, name|
|
1122
|
-
|
1286
|
+
if @options[name]
|
1287
|
+
oq << [label, @options[name],
|
1288
|
+
DISPLAY_LEVEL_ADMIN]
|
1289
|
+
end
|
1123
1290
|
end
|
1124
1291
|
|
1125
1292
|
oq.map do |label, value, level|
|
@@ -1150,7 +1317,9 @@ module MarkdownExec
|
|
1150
1317
|
def prepare_blocks_menu(blocks_in_file, opts)
|
1151
1318
|
# next if fcb.fetch(:disabled, false)
|
1152
1319
|
# next unless fcb.fetch(:name, '').present?
|
1153
|
-
blocks_in_file.map do |fcb|
|
1320
|
+
replace_consecutive_blanks(blocks_in_file).map do |fcb|
|
1321
|
+
next if Filter.prepared_not_in_menu?(opts, fcb)
|
1322
|
+
|
1154
1323
|
fcb.merge!(
|
1155
1324
|
name: fcb.dname,
|
1156
1325
|
label: BlockLabel.make(
|
@@ -1176,7 +1345,7 @@ module MarkdownExec
|
|
1176
1345
|
fcb.oname = fcb.dname = fcb.title || ''
|
1177
1346
|
return unless fcb.body
|
1178
1347
|
|
1179
|
-
|
1348
|
+
update_title_from_body(fcb)
|
1180
1349
|
|
1181
1350
|
if block &&
|
1182
1351
|
selected_messages.include?(:blocks) &&
|
@@ -1194,6 +1363,13 @@ module MarkdownExec
|
|
1194
1363
|
block.call(:line, fcb)
|
1195
1364
|
end
|
1196
1365
|
|
1366
|
+
class MenuOptions
|
1367
|
+
YES = 1
|
1368
|
+
NO = 2
|
1369
|
+
SCRIPT_TO_CLIPBOARD = 3
|
1370
|
+
SAVE_SCRIPT = 4
|
1371
|
+
end
|
1372
|
+
|
1197
1373
|
##
|
1198
1374
|
# Presents a menu to the user for approving an action and performs additional tasks based on the selection.
|
1199
1375
|
# The function provides options for approval, rejection, copying data to clipboard, or saving data to a file.
|
@@ -1211,44 +1387,40 @@ module MarkdownExec
|
|
1211
1387
|
##
|
1212
1388
|
def prompt_for_user_approval(opts, required_lines)
|
1213
1389
|
# Present a selection menu for user approval.
|
1214
|
-
|
1215
|
-
|
1216
|
-
|
1217
|
-
menu.
|
1218
|
-
menu.choice opts[:
|
1219
|
-
menu.choice opts[:
|
1390
|
+
|
1391
|
+
sel = @prompt.select(opts[:prompt_approve_block],
|
1392
|
+
filter: true) do |menu|
|
1393
|
+
menu.default MenuOptions::YES
|
1394
|
+
menu.choice opts[:prompt_yes], MenuOptions::YES
|
1395
|
+
menu.choice opts[:prompt_no], MenuOptions::NO
|
1396
|
+
menu.choice opts[:prompt_script_to_clipboard],
|
1397
|
+
MenuOptions::SCRIPT_TO_CLIPBOARD
|
1398
|
+
menu.choice opts[:prompt_save_script], MenuOptions::SAVE_SCRIPT
|
1220
1399
|
end
|
1221
1400
|
|
1222
|
-
if sel ==
|
1401
|
+
if sel == MenuOptions::SCRIPT_TO_CLIPBOARD
|
1223
1402
|
copy_to_clipboard(required_lines)
|
1224
|
-
elsif sel ==
|
1403
|
+
elsif sel == MenuOptions::SAVE_SCRIPT
|
1225
1404
|
save_to_file(opts, required_lines)
|
1226
1405
|
end
|
1227
1406
|
|
1228
|
-
sel ==
|
1407
|
+
sel == MenuOptions::YES
|
1408
|
+
rescue TTY::Reader::InputInterrupt
|
1409
|
+
exit 1
|
1229
1410
|
end
|
1230
1411
|
|
1231
|
-
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
1239
|
-
@hs_curr = state[:unit]
|
1240
|
-
@hs_rest = state[:rest]
|
1241
|
-
@options[:menu_back_at_top] ? [label] + items : items + [label]
|
1242
|
-
end
|
1243
|
-
|
1244
|
-
## insert exit option at head or tail
|
1245
|
-
#
|
1246
|
-
def prompt_menu_add_exit(items, label)
|
1247
|
-
if @options[:menu_exit_at_top]
|
1248
|
-
(@options[:menu_with_exit] ? [label] : []) + items
|
1249
|
-
else
|
1250
|
-
items + (@options[:menu_with_exit] ? [label] : [])
|
1412
|
+
def prompt_select_continue(opts)
|
1413
|
+
sel = @prompt.select(
|
1414
|
+
opts[:prompt_after_bash_exec],
|
1415
|
+
filter: true,
|
1416
|
+
quiet: true
|
1417
|
+
) do |menu|
|
1418
|
+
menu.choice opts[:prompt_yes]
|
1419
|
+
menu.choice opts[:prompt_exit]
|
1251
1420
|
end
|
1421
|
+
sel == opts[:prompt_exit] ? MenuState::EXIT : MenuState::CONTINUE
|
1422
|
+
rescue TTY::Reader::InputInterrupt
|
1423
|
+
exit 1
|
1252
1424
|
end
|
1253
1425
|
|
1254
1426
|
# :reek:UtilityFunction ### temp
|
@@ -1267,7 +1439,9 @@ module MarkdownExec
|
|
1267
1439
|
temp_blocks = []
|
1268
1440
|
|
1269
1441
|
temp_blocks_file_path = ENV.fetch('MDE_LINK_REQUIRED_FILE', nil)
|
1270
|
-
|
1442
|
+
if temp_blocks_file_path.nil? || temp_blocks_file_path.empty?
|
1443
|
+
return temp_blocks
|
1444
|
+
end
|
1271
1445
|
|
1272
1446
|
if File.exist?(temp_blocks_file_path)
|
1273
1447
|
temp_blocks = File.readlines(temp_blocks_file_path, chomp: true)
|
@@ -1276,6 +1450,24 @@ module MarkdownExec
|
|
1276
1450
|
temp_blocks
|
1277
1451
|
end
|
1278
1452
|
|
1453
|
+
# Replace duplicate blanks (where :oname is not present) with a single blank line.
|
1454
|
+
#
|
1455
|
+
# @param [Array<Hash>] lines Array of hashes to process.
|
1456
|
+
# @return [Array<Hash>] Cleaned array with consecutive blanks collapsed into one.
|
1457
|
+
def replace_consecutive_blanks(lines)
|
1458
|
+
lines.chunk_while do |i, j|
|
1459
|
+
i[:oname].to_s.empty? && j[:oname].to_s.empty?
|
1460
|
+
end.map do |chunk|
|
1461
|
+
if chunk.any? do |line|
|
1462
|
+
line[:oname].to_s.strip.empty?
|
1463
|
+
end
|
1464
|
+
chunk.first
|
1465
|
+
else
|
1466
|
+
chunk
|
1467
|
+
end
|
1468
|
+
end.flatten
|
1469
|
+
end
|
1470
|
+
|
1279
1471
|
def run
|
1280
1472
|
clear_required_file
|
1281
1473
|
execute_block_with_error_handling(initialize_and_parse_cli_options)
|
@@ -1293,7 +1485,15 @@ module MarkdownExec
|
|
1293
1485
|
|
1294
1486
|
saved_name_split filename
|
1295
1487
|
@options[:save_executed_script] = false
|
1296
|
-
select_approve_and_execute_block
|
1488
|
+
select_approve_and_execute_block
|
1489
|
+
end
|
1490
|
+
|
1491
|
+
def safeval(str)
|
1492
|
+
eval(str)
|
1493
|
+
rescue StandardError
|
1494
|
+
warn $!
|
1495
|
+
binding.pry if $tap_enable
|
1496
|
+
raise StandardError, $!
|
1297
1497
|
end
|
1298
1498
|
|
1299
1499
|
def save_to_file(opts, required_lines)
|
@@ -1320,40 +1520,48 @@ module MarkdownExec
|
|
1320
1520
|
# @param call_options [Hash] Initial options for the method.
|
1321
1521
|
# @param options_block [Block] Block of options to be merged with call_options.
|
1322
1522
|
# @return [Nil] Returns nil if no code block is selected or an error occurs.
|
1323
|
-
def select_approve_and_execute_block(call_options,
|
1324
|
-
|
1325
|
-
|
1326
|
-
|
1327
|
-
|
1523
|
+
def select_approve_and_execute_block(call_options = {},
|
1524
|
+
&options_block)
|
1525
|
+
base_opts = optsmerge(call_options, options_block)
|
1526
|
+
repeat_menu = true && !base_opts[:block_name].present?
|
1527
|
+
load_file = LoadFile::Reuse
|
1528
|
+
default = nil
|
1529
|
+
block = nil
|
1328
1530
|
|
1329
1531
|
loop do
|
1330
1532
|
loop do
|
1331
|
-
opts
|
1332
|
-
|
1333
|
-
|
1334
|
-
|
1335
|
-
|
1336
|
-
|
1337
|
-
|
1338
|
-
|
1339
|
-
|
1340
|
-
|
1341
|
-
|
1342
|
-
|
1343
|
-
|
1344
|
-
|
1345
|
-
|
1533
|
+
opts = base_opts.dup
|
1534
|
+
opts[:s_back] = false
|
1535
|
+
blocks_in_file, blocks_menu, mdoc = mdoc_menu_and_selected_from_file(opts)
|
1536
|
+
block, state = command_or_user_selected_block(blocks_in_file, blocks_menu,
|
1537
|
+
default, opts)
|
1538
|
+
return if state == MenuState::EXIT
|
1539
|
+
|
1540
|
+
load_file, next_block_name = approve_and_execute_block(block, opts,
|
1541
|
+
mdoc)
|
1542
|
+
default = load_file == LoadFile::Load ? nil : opts[:block_name]
|
1543
|
+
base_opts[:block_name] = opts[:block_name] = next_block_name
|
1544
|
+
base_opts[:filename] = opts[:filename]
|
1545
|
+
|
1546
|
+
# user prompt to exit if the menu will be displayed again
|
1547
|
+
#
|
1548
|
+
if repeat_menu &&
|
1549
|
+
block[:shell] == BlockType::BASH &&
|
1550
|
+
opts[:pause_after_bash_exec] &&
|
1551
|
+
prompt_select_continue(opts) == MenuState::EXIT
|
1552
|
+
return
|
1346
1553
|
end
|
1347
1554
|
|
1348
|
-
|
1349
|
-
|
1350
|
-
|
1351
|
-
|
1555
|
+
# exit current document/menu if loading next document or single block_name was specified
|
1556
|
+
#
|
1557
|
+
if state == MenuState::CONTINUE && load_file == LoadFile::Load
|
1558
|
+
break
|
1559
|
+
end
|
1352
1560
|
break unless repeat_menu
|
1353
1561
|
end
|
1354
|
-
break if load_file
|
1562
|
+
break if load_file == LoadFile::Reuse
|
1355
1563
|
|
1356
|
-
repeat_menu = next_block_name_from_command_line_arguments(
|
1564
|
+
repeat_menu = next_block_name_from_command_line_arguments(base_opts)
|
1357
1565
|
end
|
1358
1566
|
rescue StandardError => err
|
1359
1567
|
warn(error = "ERROR ** MarkParse.select_approve_and_execute_block(); #{err.inspect}")
|
@@ -1383,21 +1591,24 @@ module MarkdownExec
|
|
1383
1591
|
# Presents a TTY prompt to select an option or exit, returns metadata including option and selected
|
1384
1592
|
def select_option_with_metadata(prompt_text, items, opts = {})
|
1385
1593
|
selection = @prompt.select(prompt_text,
|
1386
|
-
|
1387
|
-
prompt_menu_add_back(
|
1388
|
-
items,
|
1389
|
-
opts[:menu_option_back_name]
|
1390
|
-
),
|
1391
|
-
opts[:menu_option_exit_name]
|
1392
|
-
),
|
1594
|
+
items,
|
1393
1595
|
opts.merge(filter: true))
|
1394
|
-
|
1395
|
-
|
1396
|
-
|
1397
|
-
|
1398
|
-
|
1399
|
-
|
1400
|
-
|
1596
|
+
|
1597
|
+
items.find { |item| item[:dname] == selection }
|
1598
|
+
.merge(
|
1599
|
+
if selection == menu_chrome_colored_option(opts,
|
1600
|
+
:menu_option_back_name)
|
1601
|
+
{ option: selection, curr: @hs_curr, rest: @hs_rest,
|
1602
|
+
shell: BlockType::LINK }
|
1603
|
+
elsif selection == menu_chrome_colored_option(opts,
|
1604
|
+
:menu_option_exit_name)
|
1605
|
+
{ option: selection }
|
1606
|
+
else
|
1607
|
+
{ selected: selection }
|
1608
|
+
end
|
1609
|
+
)
|
1610
|
+
rescue TTY::Reader::InputInterrupt
|
1611
|
+
exit 1
|
1401
1612
|
end
|
1402
1613
|
|
1403
1614
|
def select_recent_output
|
@@ -1429,21 +1640,13 @@ module MarkdownExec
|
|
1429
1640
|
|
1430
1641
|
saved_name_split(filename)
|
1431
1642
|
|
1432
|
-
select_approve_and_execute_block({
|
1433
|
-
bash: true,
|
1643
|
+
select_approve_and_execute_block({ bash: true,
|
1434
1644
|
save_executed_script: false,
|
1435
|
-
struct: true
|
1436
|
-
})
|
1437
|
-
end
|
1438
|
-
|
1439
|
-
# set the title of an FCB object based on its body if it is nil or empty
|
1440
|
-
def set_fcb_title(fcb)
|
1441
|
-
return unless fcb.title.nil? || fcb.title.empty?
|
1442
|
-
|
1443
|
-
fcb.title = (fcb&.body || []).join(' ').gsub(/ +/, ' ')[0..64]
|
1645
|
+
struct: true })
|
1444
1646
|
end
|
1445
1647
|
|
1446
|
-
def start_fenced_block(opts, line, headings,
|
1648
|
+
def start_fenced_block(opts, line, headings,
|
1649
|
+
fenced_start_extended_regex)
|
1447
1650
|
fcb_title_groups = line.match(fenced_start_extended_regex).named_captures.sym_keys
|
1448
1651
|
rest = fcb_title_groups.fetch(:rest, '')
|
1449
1652
|
|
@@ -1476,7 +1679,11 @@ module MarkdownExec
|
|
1476
1679
|
end
|
1477
1680
|
|
1478
1681
|
def tty_prompt_without_disabled_symbol
|
1479
|
-
TTY::Prompt.new(interrupt:
|
1682
|
+
TTY::Prompt.new(interrupt: lambda {
|
1683
|
+
puts;
|
1684
|
+
raise TTY::Reader::InputInterrupt
|
1685
|
+
},
|
1686
|
+
symbols: { cross: ' ' })
|
1480
1687
|
end
|
1481
1688
|
|
1482
1689
|
##
|
@@ -1523,7 +1730,8 @@ module MarkdownExec
|
|
1523
1730
|
#
|
1524
1731
|
# @return [Void] The function modifies the `state` and `selected_messages` arguments in place.
|
1525
1732
|
##
|
1526
|
-
def update_line_and_block_state(line, state, opts, selected_messages,
|
1733
|
+
def update_line_and_block_state(line, state, opts, selected_messages,
|
1734
|
+
&block)
|
1527
1735
|
if opts[:menu_blocks_with_headings]
|
1528
1736
|
state[:headings] =
|
1529
1737
|
update_document_headings(line, state[:headings], opts)
|
@@ -1531,7 +1739,8 @@ module MarkdownExec
|
|
1531
1739
|
|
1532
1740
|
if line.match(state[:fenced_start_and_end_regex])
|
1533
1741
|
if state[:in_fenced_block]
|
1534
|
-
process_fenced_block(state[:fcb], opts, selected_messages,
|
1742
|
+
process_fenced_block(state[:fcb], opts, selected_messages,
|
1743
|
+
&block)
|
1535
1744
|
state[:in_fenced_block] = false
|
1536
1745
|
else
|
1537
1746
|
state[:fcb] =
|
@@ -1557,26 +1766,59 @@ module MarkdownExec
|
|
1557
1766
|
@options
|
1558
1767
|
end
|
1559
1768
|
|
1560
|
-
|
1769
|
+
# Updates the title of an FCB object from its body content if the title is nil or empty.
|
1770
|
+
def update_title_from_body(fcb)
|
1771
|
+
return unless fcb.title.nil? || fcb.title.empty?
|
1772
|
+
|
1773
|
+
fcb.title = derive_title_from_body(fcb)
|
1774
|
+
end
|
1775
|
+
|
1776
|
+
def wait_for_user_selected_block(blocks_in_file, blocks_menu,
|
1777
|
+
default, opts)
|
1778
|
+
block, state = wait_for_user_selection(blocks_in_file, blocks_menu,
|
1779
|
+
default, opts)
|
1780
|
+
case state
|
1781
|
+
when MenuState::BACK
|
1782
|
+
opts[:block_name] = block[:dname]
|
1783
|
+
opts[:s_back] = true
|
1784
|
+
when MenuState::CONTINUE
|
1785
|
+
opts[:block_name] = block[:dname]
|
1786
|
+
end
|
1787
|
+
|
1788
|
+
[block, state]
|
1789
|
+
end
|
1790
|
+
|
1791
|
+
## Handles the menu interaction and returns selected block and option state
|
1561
1792
|
#
|
1562
|
-
def wait_for_user_selection(blocks_in_file, blocks_menu, default,
|
1793
|
+
def wait_for_user_selection(blocks_in_file, blocks_menu, default,
|
1794
|
+
opts)
|
1563
1795
|
pt = opts[:prompt_select_block].to_s
|
1564
1796
|
bm = prepare_blocks_menu(blocks_menu, opts)
|
1565
|
-
return [nil,
|
1797
|
+
return [nil, MenuState::EXIT] if bm.count.zero?
|
1798
|
+
|
1799
|
+
o2 = if default
|
1800
|
+
opts.merge(default: default)
|
1801
|
+
else
|
1802
|
+
opts
|
1803
|
+
end
|
1566
1804
|
|
1567
|
-
obj = select_option_with_metadata(pt, bm,
|
1568
|
-
default: default,
|
1805
|
+
obj = select_option_with_metadata(pt, bm, o2.merge(
|
1569
1806
|
per_page: opts[:select_page_height]
|
1570
1807
|
))
|
1571
|
-
|
1572
|
-
|
1573
|
-
|
1574
|
-
|
1575
|
-
|
1808
|
+
|
1809
|
+
case obj.fetch(:oname, nil)
|
1810
|
+
when menu_chrome_formatted_option(opts, :menu_option_exit_name)
|
1811
|
+
[nil, MenuState::EXIT]
|
1812
|
+
when menu_chrome_formatted_option(opts, :menu_option_back_name)
|
1813
|
+
[obj, MenuState::BACK]
|
1576
1814
|
else
|
1577
|
-
|
1578
|
-
[label_block.oname, :continue]
|
1815
|
+
[obj, MenuState::CONTINUE]
|
1579
1816
|
end
|
1817
|
+
rescue StandardError => err
|
1818
|
+
warn(error = "ERROR ** MarkParse.wait_for_user_selection(); #{err.inspect}")
|
1819
|
+
warn caller.take(3)
|
1820
|
+
binding.pry if $tap_enable
|
1821
|
+
raise ArgumentError, error
|
1580
1822
|
end
|
1581
1823
|
|
1582
1824
|
# Handles the core logic for generating the command file's metadata and content.
|
@@ -1593,7 +1835,8 @@ module MarkdownExec
|
|
1593
1835
|
|
1594
1836
|
@execute_script_filespec =
|
1595
1837
|
@options[:saved_filespec] =
|
1596
|
-
File.join opts[:saved_script_folder],
|
1838
|
+
File.join opts[:saved_script_folder],
|
1839
|
+
opts[:saved_script_filename]
|
1597
1840
|
|
1598
1841
|
shebang = if @options[:shebang]&.present?
|
1599
1842
|
"#{@options[:shebang]} #{@options[:shell]}\n"
|
@@ -1656,7 +1899,7 @@ if $PROGRAM_NAME == __FILE__
|
|
1656
1899
|
|
1657
1900
|
def test_calling_execute_approved_block_calls_command_execute_with_argument_args_value
|
1658
1901
|
pigeon = 'E'
|
1659
|
-
obj = {
|
1902
|
+
obj = { s_pass_args: pigeon }
|
1660
1903
|
|
1661
1904
|
c = MarkdownExec::MarkParse.new
|
1662
1905
|
|
@@ -1676,15 +1919,17 @@ if $PROGRAM_NAME == __FILE__
|
|
1676
1919
|
end
|
1677
1920
|
|
1678
1921
|
def test_set_fcb_title
|
1679
|
-
# sample input and output data for testing
|
1922
|
+
# sample input and output data for testing update_title_from_body method
|
1680
1923
|
input_output_data = [
|
1681
1924
|
{
|
1682
1925
|
input: FCB.new(title: nil, body: ["puts 'Hello, world!'"]),
|
1683
1926
|
output: "puts 'Hello, world!'"
|
1684
1927
|
},
|
1685
1928
|
{
|
1686
|
-
input: FCB.new(title: '',
|
1687
|
-
|
1929
|
+
input: FCB.new(title: '',
|
1930
|
+
body: ['def add(x, y)',
|
1931
|
+
' x + y', 'end']),
|
1932
|
+
output: "def add(x, y)\n x + y\n end\n"
|
1688
1933
|
},
|
1689
1934
|
{
|
1690
1935
|
input: FCB.new(title: 'foo', body: %w[bar baz]),
|
@@ -1697,10 +1942,20 @@ if $PROGRAM_NAME == __FILE__
|
|
1697
1942
|
input_output_data.each do |data|
|
1698
1943
|
input = data[:input]
|
1699
1944
|
output = data[:output]
|
1700
|
-
@mark_parse.
|
1945
|
+
@mark_parse.update_title_from_body(input)
|
1701
1946
|
assert_equal output, input.title
|
1702
1947
|
end
|
1703
1948
|
end
|
1704
1949
|
end
|
1705
|
-
|
1706
|
-
|
1950
|
+
|
1951
|
+
def test_select_block
|
1952
|
+
blocks = [block1, block2]
|
1953
|
+
menu = [m1, m2]
|
1954
|
+
|
1955
|
+
block, state = obj.select_block(blocks, menu, nil, {})
|
1956
|
+
|
1957
|
+
assert_equal block1, block
|
1958
|
+
assert_equal MenuState::CONTINUE, state
|
1959
|
+
end
|
1960
|
+
end # module MarkdownExec
|
1961
|
+
end # if
|