markdown_exec 1.8.9 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +5 -1
- data/CHANGELOG.md +12 -0
- data/Gemfile +3 -2
- data/Gemfile.lock +2 -8
- data/bin/tab_completion.sh +11 -3
- data/examples/colors.md +41 -19
- data/examples/document_options.md +8 -0
- data/examples/duplicate_block.md +5 -8
- data/examples/import0.md +3 -5
- data/examples/import1.md +1 -1
- data/examples/include.md +16 -10
- data/examples/indent.md +2 -0
- data/examples/index.md +68 -0
- data/examples/linked.md +31 -0
- data/examples/llm.md +54 -0
- data/lib/find_files.rb +1 -2
- data/lib/hash_delegator.rb +483 -207
- data/lib/input_sequencer.rb +229 -0
- data/lib/link_history.rb +11 -5
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/markdown_exec.rb +4 -6
- data/lib/mdoc.rb +7 -7
- data/lib/menu.src.yml +59 -9
- data/lib/menu.yml +52 -10
- metadata +6 -3
- data/examples/infile_config.md +0 -10
data/lib/hash_delegator.rb
CHANGED
@@ -11,6 +11,7 @@ require 'optparse'
|
|
11
11
|
require 'set'
|
12
12
|
require 'shellwords'
|
13
13
|
require 'tmpdir'
|
14
|
+
# require 'tty-file'
|
14
15
|
require 'tty-prompt'
|
15
16
|
require 'yaml'
|
16
17
|
|
@@ -40,10 +41,6 @@ class String
|
|
40
41
|
end
|
41
42
|
|
42
43
|
module HashDelegatorSelf
|
43
|
-
# def add_back_option(menu_blocks)
|
44
|
-
# append_chrome_block(menu_blocks, MenuState::BACK)
|
45
|
-
# end
|
46
|
-
|
47
44
|
# Applies an ANSI color method to a string using a specified color key.
|
48
45
|
# The method retrieves the color method from the provided hash. If the color key
|
49
46
|
# is not present in the hash, it uses a default color method.
|
@@ -186,7 +183,7 @@ module HashDelegatorSelf
|
|
186
183
|
end
|
187
184
|
|
188
185
|
def join_code_lines(lines)
|
189
|
-
((lines || [])+ ['']).join("\n")
|
186
|
+
((lines || []) + ['']).join("\n")
|
190
187
|
end
|
191
188
|
|
192
189
|
def merge_lists(*args)
|
@@ -195,10 +192,10 @@ module HashDelegatorSelf
|
|
195
192
|
merged.empty? ? [] : merged
|
196
193
|
end
|
197
194
|
|
198
|
-
def next_link_state(block_name_from_cli
|
195
|
+
def next_link_state(block_name_from_cli:, was_using_cli:, block_state:, block_name: nil)
|
199
196
|
# &bsp 'next_link_state', block_name_from_cli, was_using_cli, block_state
|
200
197
|
# Set block_name based on block_name_from_cli
|
201
|
-
block_name =
|
198
|
+
block_name = @cli_block_name if block_name_from_cli
|
202
199
|
# &bsp 'block_name:', block_name
|
203
200
|
|
204
201
|
# Determine the state of breaker based on was_using_cli and the block type
|
@@ -214,6 +211,8 @@ module HashDelegatorSelf
|
|
214
211
|
|
215
212
|
def parse_yaml_data_from_body(body)
|
216
213
|
body.any? ? YAML.load(body.join("\n")) : {}
|
214
|
+
rescue StandardError
|
215
|
+
error_handler('parse_yaml_data_from_body', { abort: true })
|
217
216
|
end
|
218
217
|
|
219
218
|
# Reads required code blocks from a temporary file specified by an environment variable.
|
@@ -268,21 +267,15 @@ module HashDelegatorSelf
|
|
268
267
|
# @param fcb [Object] The fcb object whose attributes are to be updated.
|
269
268
|
# @param selected_messages [Array<Symbol>] A list of message types to determine if yielding is applicable.
|
270
269
|
# @param block [Block] An optional block to yield to if conditions are met.
|
271
|
-
def update_menu_attrib_yield_selected(fcb
|
270
|
+
def update_menu_attrib_yield_selected(fcb:, messages:, configuration: {}, &block)
|
272
271
|
initialize_fcb_names(fcb)
|
273
272
|
return unless fcb.body
|
274
273
|
|
275
274
|
default_block_title_from_body(fcb)
|
276
|
-
MarkdownExec::Filter.yield_to_block_if_applicable(fcb,
|
275
|
+
MarkdownExec::Filter.yield_to_block_if_applicable(fcb, messages, configuration,
|
277
276
|
&block)
|
278
277
|
end
|
279
278
|
|
280
|
-
# Writes the provided code blocks to a file.
|
281
|
-
# @param code_blocks [String] Code blocks to write into the file.
|
282
|
-
def write_code_to_file(content, path)
|
283
|
-
File.write(path, content)
|
284
|
-
end
|
285
|
-
|
286
279
|
def write_execution_output_to_file(files, filespec)
|
287
280
|
FileUtils.mkdir_p File.dirname(filespec)
|
288
281
|
|
@@ -308,7 +301,6 @@ module HashDelegatorSelf
|
|
308
301
|
block.call(:line, MarkdownExec::FCB.new(body: [line]))
|
309
302
|
end
|
310
303
|
end
|
311
|
-
### require_relative 'hash_delegator_self'
|
312
304
|
|
313
305
|
# This module provides methods for compacting and converting data structures.
|
314
306
|
module CompactionHelpers
|
@@ -412,40 +404,37 @@ module MarkdownExec
|
|
412
404
|
# along with initial and final dividers, based on the delegate object's configuration.
|
413
405
|
#
|
414
406
|
# @param menu_blocks [Array] The array of menu block elements to be modified.
|
415
|
-
def add_menu_chrome_blocks!(menu_blocks
|
407
|
+
def add_menu_chrome_blocks!(menu_blocks:, link_state:)
|
416
408
|
return unless @delegate_object[:menu_link_format].present?
|
417
409
|
|
418
|
-
if @delegate_object[:menu_with_inherited_lines]
|
419
|
-
add_inherited_lines(menu_blocks,
|
420
|
-
link_state)
|
421
|
-
end
|
410
|
+
add_inherited_lines(menu_blocks: menu_blocks, link_state: link_state) if @delegate_object[:menu_with_inherited_lines]
|
422
411
|
|
423
412
|
# back before exit
|
424
|
-
add_back_option(menu_blocks) if should_add_back_option?
|
413
|
+
add_back_option(menu_blocks: menu_blocks) if should_add_back_option?
|
425
414
|
|
426
415
|
# exit after other options
|
427
|
-
add_exit_option(menu_blocks) if @delegate_object[:menu_with_exit]
|
416
|
+
add_exit_option(menu_blocks: menu_blocks) if @delegate_object[:menu_with_exit]
|
428
417
|
|
429
|
-
add_dividers(menu_blocks)
|
418
|
+
add_dividers(menu_blocks: menu_blocks)
|
430
419
|
end
|
431
420
|
|
432
421
|
private
|
433
422
|
|
434
|
-
def add_back_option(menu_blocks)
|
435
|
-
append_chrome_block(menu_blocks, MenuState::BACK)
|
423
|
+
def add_back_option(menu_blocks:)
|
424
|
+
append_chrome_block(menu_blocks: menu_blocks, menu_state: MenuState::BACK)
|
436
425
|
end
|
437
426
|
|
438
|
-
def add_dividers(menu_blocks)
|
439
|
-
append_divider(menu_blocks, :initial)
|
440
|
-
append_divider(menu_blocks, :final)
|
427
|
+
def add_dividers(menu_blocks:)
|
428
|
+
append_divider(menu_blocks: menu_blocks, position: :initial)
|
429
|
+
append_divider(menu_blocks: menu_blocks, position: :final)
|
441
430
|
end
|
442
431
|
|
443
|
-
def add_exit_option(menu_blocks)
|
444
|
-
append_chrome_block(menu_blocks, MenuState::EXIT)
|
432
|
+
def add_exit_option(menu_blocks:)
|
433
|
+
append_chrome_block(menu_blocks: menu_blocks, menu_state: MenuState::EXIT)
|
445
434
|
end
|
446
435
|
|
447
|
-
def add_inherited_lines(menu_blocks
|
448
|
-
append_inherited_lines(menu_blocks, link_state)
|
436
|
+
def add_inherited_lines(menu_blocks:, link_state:)
|
437
|
+
append_inherited_lines(menu_blocks: menu_blocks, link_state: link_state)
|
449
438
|
end
|
450
439
|
|
451
440
|
public
|
@@ -454,8 +443,8 @@ module MarkdownExec
|
|
454
443
|
#
|
455
444
|
# @param all_blocks [Array] The current blocks in the menu
|
456
445
|
# @param type [Symbol] The type of chrome block to add (:back or :exit)
|
457
|
-
def append_chrome_block(menu_blocks
|
458
|
-
case
|
446
|
+
def append_chrome_block(menu_blocks:, menu_state:)
|
447
|
+
case menu_state
|
459
448
|
when MenuState::BACK
|
460
449
|
history_state_partition
|
461
450
|
option_name = @delegate_object[:menu_option_back_name]
|
@@ -487,7 +476,7 @@ module MarkdownExec
|
|
487
476
|
#
|
488
477
|
# @param menu_blocks [Array] The array of menu block elements.
|
489
478
|
# @param position [Symbol] The position to insert the divider (:initial or :final).
|
490
|
-
def append_inherited_lines(menu_blocks
|
479
|
+
def append_inherited_lines(menu_blocks:, link_state:, position: top)
|
491
480
|
return unless link_state.inherited_lines.present?
|
492
481
|
|
493
482
|
insert_at_top = @delegate_object[:menu_inherited_lines_at_top]
|
@@ -520,7 +509,7 @@ module MarkdownExec
|
|
520
509
|
#
|
521
510
|
# @param menu_blocks [Array] The array of menu block elements.
|
522
511
|
# @param position [Symbol] The position to insert the divider (:initial or :final).
|
523
|
-
def append_divider(menu_blocks
|
512
|
+
def append_divider(menu_blocks:, position:)
|
524
513
|
return unless divider_formatting_present?(position)
|
525
514
|
|
526
515
|
divider = create_divider(position)
|
@@ -542,6 +531,15 @@ module MarkdownExec
|
|
542
531
|
end
|
543
532
|
end
|
544
533
|
|
534
|
+
def assign_key_value_in_bash(key, value)
|
535
|
+
if value =~ /["$\\`]/
|
536
|
+
# requiring ShellWords to write into Bash scripts
|
537
|
+
"#{key}=#{Shellwords.escape(value)}"
|
538
|
+
else
|
539
|
+
"#{key}=\"#{value}\""
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
545
543
|
# private
|
546
544
|
|
547
545
|
# Iterates through nested files to collect various types of blocks, including dividers, tasks, and others.
|
@@ -603,9 +601,9 @@ module MarkdownExec
|
|
603
601
|
# @param mdoc [YourMDocClass] An instance of the MDoc class.
|
604
602
|
# @param selected [Hash] The selected block.
|
605
603
|
# @return [Array<String>] Required code blocks as an array of lines.
|
606
|
-
def collect_required_code_lines(mdoc
|
604
|
+
def collect_required_code_lines(mdoc:, selected:, block_source:, link_state: LinkState.new)
|
607
605
|
required = mdoc.collect_recursively_required_code(
|
608
|
-
selected[:nickname] || selected[:oname],
|
606
|
+
anyname: selected[:nickname] || selected[:oname],
|
609
607
|
label_format_above: @delegate_object[:shell_code_label_format_above],
|
610
608
|
label_format_below: @delegate_object[:shell_code_label_format_below],
|
611
609
|
block_source: block_source
|
@@ -621,7 +619,7 @@ module MarkdownExec
|
|
621
619
|
runtime_exception(:runtime_exception_error_level,
|
622
620
|
'unmet_dependencies, flag: runtime_exception_error_level',
|
623
621
|
required[:unmet_dependencies])
|
624
|
-
|
622
|
+
else
|
625
623
|
warn format_and_highlight_dependencies(dependencies,
|
626
624
|
highlight: [@delegate_object[:block_name]])
|
627
625
|
end
|
@@ -667,14 +665,14 @@ module MarkdownExec
|
|
667
665
|
'-c', command,
|
668
666
|
@delegate_object[:filename],
|
669
667
|
*args) do |stdin, stdout, stderr, exec_thr|
|
670
|
-
handle_stream(stdout, ExecutionStreams::StdOut) do |line|
|
668
|
+
handle_stream(stream: stdout, file_type: ExecutionStreams::StdOut) do |line|
|
671
669
|
yield nil, line, nil, exec_thr if block_given?
|
672
670
|
end
|
673
|
-
handle_stream(stderr, ExecutionStreams::StdErr) do |line|
|
671
|
+
handle_stream(stream: stderr, file_type: ExecutionStreams::StdErr) do |line|
|
674
672
|
yield nil, nil, line, exec_thr if block_given?
|
675
673
|
end
|
676
674
|
|
677
|
-
in_thr = handle_stream($stdin, ExecutionStreams::StdIn) do |line|
|
675
|
+
in_thr = handle_stream(stream: $stdin, file_type: ExecutionStreams::StdIn) do |line|
|
678
676
|
stdin.puts(line)
|
679
677
|
yield line, nil, nil, exec_thr if block_given?
|
680
678
|
end
|
@@ -703,7 +701,7 @@ module MarkdownExec
|
|
703
701
|
@fout.fout "Error ENOENT: #{err.inspect}"
|
704
702
|
end
|
705
703
|
|
706
|
-
def load_cli_or_user_selected_block(all_blocks, menu_blocks, default)
|
704
|
+
def load_cli_or_user_selected_block(all_blocks: [], menu_blocks: [], default: nil)
|
707
705
|
if @delegate_object[:block_name].present?
|
708
706
|
block = all_blocks.find do |item|
|
709
707
|
item[:oname] == @delegate_object[:block_name]
|
@@ -727,18 +725,18 @@ module MarkdownExec
|
|
727
725
|
# @param mdoc [Object] The markdown document object containing code blocks.
|
728
726
|
# @param selected [Hash] The selected item from the menu to be executed.
|
729
727
|
# @return [LoadFileLinkState] An object indicating whether to load the next block or reuse the current one.
|
730
|
-
def compile_execute_and_trigger_reuse(mdoc
|
731
|
-
required_lines = collect_required_code_lines(mdoc, selected, link_state,
|
728
|
+
def compile_execute_and_trigger_reuse(mdoc:, selected:, block_source:, link_state: nil)
|
729
|
+
required_lines = collect_required_code_lines(mdoc: mdoc, selected: selected, link_state: link_state,
|
732
730
|
block_source: block_source)
|
733
731
|
output_or_approval = @delegate_object[:output_script] || @delegate_object[:user_must_approve]
|
734
|
-
display_required_code(required_lines) if output_or_approval
|
732
|
+
display_required_code(required_lines: required_lines) if output_or_approval
|
735
733
|
allow_execution = if @delegate_object[:user_must_approve]
|
736
|
-
prompt_for_user_approval(required_lines, selected)
|
734
|
+
prompt_for_user_approval(required_lines: required_lines, selected: selected)
|
737
735
|
else
|
738
736
|
true
|
739
737
|
end
|
740
738
|
|
741
|
-
execute_required_lines(required_lines, selected) if allow_execution
|
739
|
+
execute_required_lines(required_lines: required_lines, selected: selected) if allow_execution
|
742
740
|
|
743
741
|
link_state.block_name = nil
|
744
742
|
LoadFileLinkState.new(LoadFile::Reuse, link_state)
|
@@ -770,8 +768,8 @@ module MarkdownExec
|
|
770
768
|
# @param match_data [MatchData] The match data containing named captures for formatting.
|
771
769
|
# @param format_option [String] The format string to be used for the new block.
|
772
770
|
# @param color_method [Symbol] The color method to apply to the block's display name.
|
773
|
-
def create_and_add_chrome_block(blocks
|
774
|
-
color_method)
|
771
|
+
def create_and_add_chrome_block(blocks:, match_data:, format_option:,
|
772
|
+
color_method:)
|
775
773
|
oname = format(format_option,
|
776
774
|
match_data.named_captures.transform_keys(&:to_sym))
|
777
775
|
blocks.push FCB.new(
|
@@ -804,8 +802,12 @@ module MarkdownExec
|
|
804
802
|
next
|
805
803
|
end
|
806
804
|
|
807
|
-
create_and_add_chrome_block(
|
808
|
-
|
805
|
+
create_and_add_chrome_block(
|
806
|
+
blocks: blocks,
|
807
|
+
match_data: mbody,
|
808
|
+
format_option: @delegate_object[criteria[:format]],
|
809
|
+
color_method: @delegate_object[criteria[:color]].to_sym
|
810
|
+
)
|
809
811
|
break
|
810
812
|
end
|
811
813
|
end
|
@@ -833,9 +835,7 @@ module MarkdownExec
|
|
833
835
|
return true if @run_state.block_name_from_cli
|
834
836
|
|
835
837
|
# return false if @prior_execution_block == @delegate_object[:block_name]
|
836
|
-
if @prior_execution_block == @delegate_object[:block_name]
|
837
|
-
return @allowed_execution_block == @prior_execution_block || prompt_approve_repeat
|
838
|
-
end
|
838
|
+
return @allowed_execution_block == @prior_execution_block || prompt_approve_repeat if @prior_execution_block == @delegate_object[:block_name]
|
839
839
|
|
840
840
|
@prior_execution_block = @delegate_object[:block_name]
|
841
841
|
@allowed_execution_block = nil
|
@@ -869,7 +869,7 @@ module MarkdownExec
|
|
869
869
|
# It wraps the code lines between a formatted header and tail.
|
870
870
|
#
|
871
871
|
# @param required_lines [Array<String>] The lines of code to be displayed.
|
872
|
-
def display_required_code(required_lines)
|
872
|
+
def display_required_code(required_lines:)
|
873
873
|
output_color_formatted(:script_preview_head,
|
874
874
|
:script_preview_frame_color)
|
875
875
|
required_lines.each { |cb| @fout.fout cb }
|
@@ -896,10 +896,10 @@ module MarkdownExec
|
|
896
896
|
#
|
897
897
|
# @param required_lines [Array<String>] The lines of code to be executed.
|
898
898
|
# @param selected [FCB] The selected functional code block object.
|
899
|
-
def execute_required_lines(required_lines
|
900
|
-
write_command_file(required_lines, selected) if @delegate_object[:save_executed_script]
|
899
|
+
def execute_required_lines(required_lines: [], selected: FCB.new)
|
900
|
+
write_command_file(required_lines: required_lines, selected: selected) if @delegate_object[:save_executed_script]
|
901
901
|
calc_logged_stdout_filename
|
902
|
-
format_and_execute_command(required_lines)
|
902
|
+
format_and_execute_command(code_lines: required_lines)
|
903
903
|
post_execution_process
|
904
904
|
end
|
905
905
|
|
@@ -912,12 +912,14 @@ module MarkdownExec
|
|
912
912
|
# @param opts [Hash] Options hash containing configuration settings.
|
913
913
|
# @param mdoc [YourMDocClass] An instance of the MDoc class.
|
914
914
|
#
|
915
|
-
def execute_shell_type(selected
|
916
|
-
block_source:)
|
915
|
+
def execute_shell_type(selected:, mdoc:, block_source:, link_state: LinkState.new)
|
917
916
|
if selected.fetch(:shell, '') == BlockType::LINK
|
918
917
|
debounce_reset
|
919
|
-
push_link_history_and_trigger_load(selected.fetch(:body, ''),
|
920
|
-
|
918
|
+
push_link_history_and_trigger_load(link_block_body: selected.fetch(:body, ''),
|
919
|
+
mdoc: mdoc,
|
920
|
+
selected: selected,
|
921
|
+
link_state: link_state,
|
922
|
+
block_source: block_source)
|
921
923
|
|
922
924
|
elsif @menu_user_clicked_back_link
|
923
925
|
debounce_reset
|
@@ -925,10 +927,29 @@ module MarkdownExec
|
|
925
927
|
|
926
928
|
elsif selected[:shell] == BlockType::OPTS
|
927
929
|
debounce_reset
|
928
|
-
|
930
|
+
block_names = []
|
931
|
+
code_lines = []
|
932
|
+
dependencies = {}
|
933
|
+
options_state = read_show_options_and_trigger_reuse(selected: selected, link_state: link_state)
|
934
|
+
|
935
|
+
## apply options to current state
|
936
|
+
#
|
929
937
|
@menu_base_options.merge!(options_state.options)
|
930
938
|
@delegate_object.merge!(options_state.options)
|
931
|
-
|
939
|
+
|
940
|
+
### options_state.load_file_link_state
|
941
|
+
link_state = LinkState.new
|
942
|
+
link_history_push_and_next(
|
943
|
+
curr_block_name: selected[:oname],
|
944
|
+
curr_document_filename: @delegate_object[:filename],
|
945
|
+
inherited_block_names: ((link_state&.inherited_block_names || []) + block_names).sort.uniq,
|
946
|
+
inherited_dependencies: (link_state&.inherited_dependencies || {}).merge(dependencies || {}), ### merge, not replace, key data
|
947
|
+
inherited_lines: HashDelegator.code_merge(link_state&.inherited_lines, code_lines),
|
948
|
+
next_block_name: '',
|
949
|
+
next_document_filename: @delegate_object[:filename],
|
950
|
+
next_load_file: LoadFile::Reuse
|
951
|
+
)
|
952
|
+
|
932
953
|
|
933
954
|
elsif selected[:shell] == BlockType::VARS
|
934
955
|
debounce_reset
|
@@ -947,7 +968,9 @@ module MarkdownExec
|
|
947
968
|
)
|
948
969
|
|
949
970
|
elsif debounce_allows
|
950
|
-
compile_execute_and_trigger_reuse(mdoc
|
971
|
+
compile_execute_and_trigger_reuse(mdoc: mdoc,
|
972
|
+
selected: selected,
|
973
|
+
link_state: link_state,
|
951
974
|
block_source: block_source)
|
952
975
|
else
|
953
976
|
LoadFileLinkState.new(LoadFile::Reuse, link_state)
|
@@ -968,8 +991,8 @@ module MarkdownExec
|
|
968
991
|
string_send_color(data_string, color_sym)
|
969
992
|
end
|
970
993
|
|
971
|
-
def format_and_execute_command(
|
972
|
-
formatted_command =
|
994
|
+
def format_and_execute_command(code_lines:)
|
995
|
+
formatted_command = code_lines.flatten.join("\n")
|
973
996
|
@fout.fout fetch_color(data_sym: :script_execution_head,
|
974
997
|
color_sym: :script_execution_frame_color)
|
975
998
|
command_execute(formatted_command, args: @pass_args)
|
@@ -1052,7 +1075,7 @@ module MarkdownExec
|
|
1052
1075
|
@menu_user_clicked_back_link = block_state.state == MenuState::BACK
|
1053
1076
|
end
|
1054
1077
|
|
1055
|
-
def handle_stream(stream
|
1078
|
+
def handle_stream(stream:, file_type:, swap: false)
|
1056
1079
|
@process_mutex.synchronize do
|
1057
1080
|
Thread.new do
|
1058
1081
|
stream.each_line do |line|
|
@@ -1106,7 +1129,7 @@ module MarkdownExec
|
|
1106
1129
|
end
|
1107
1130
|
end
|
1108
1131
|
|
1109
|
-
def link_block_data_eval(link_state, code_lines, selected, link_block_data)
|
1132
|
+
def link_block_data_eval(link_state, code_lines, selected, link_block_data, block_source:)
|
1110
1133
|
all_code = HashDelegator.code_merge(link_state&.inherited_lines, code_lines)
|
1111
1134
|
|
1112
1135
|
if link_block_data.fetch(LinkKeys::Exec, false)
|
@@ -1117,14 +1140,14 @@ module MarkdownExec
|
|
1117
1140
|
@delegate_object[:shell],
|
1118
1141
|
'-c', all_code.join("\n")
|
1119
1142
|
) do |stdin, stdout, stderr, _exec_thr|
|
1120
|
-
handle_stream(stdout, ExecutionStreams::StdOut) do |line|
|
1143
|
+
handle_stream(stream: stdout, file_type: ExecutionStreams::StdOut) do |line|
|
1121
1144
|
output_lines.push(line)
|
1122
1145
|
end
|
1123
|
-
handle_stream(stderr, ExecutionStreams::StdErr) do |line|
|
1146
|
+
handle_stream(stream: stderr, file_type: ExecutionStreams::StdErr) do |line|
|
1124
1147
|
output_lines.push(line)
|
1125
1148
|
end
|
1126
1149
|
|
1127
|
-
in_thr = handle_stream($stdin, ExecutionStreams::StdIn) do |line|
|
1150
|
+
in_thr = handle_stream(stream: $stdin, file_type: ExecutionStreams::StdIn) do |line|
|
1128
1151
|
stdin.puts(line)
|
1129
1152
|
end
|
1130
1153
|
|
@@ -1147,13 +1170,10 @@ module MarkdownExec
|
|
1147
1170
|
output_lines = `#{all_code.join("\n")}`.split("\n")
|
1148
1171
|
end
|
1149
1172
|
|
1150
|
-
unless output_lines
|
1151
|
-
HashDelegator.error_handler('all_code eval output_lines is nil', { abort: true })
|
1152
|
-
end
|
1173
|
+
HashDelegator.error_handler('all_code eval output_lines is nil', { abort: true }) unless output_lines
|
1153
1174
|
|
1154
1175
|
label_format_above = @delegate_object[:shell_code_label_format_above]
|
1155
1176
|
label_format_below = @delegate_object[:shell_code_label_format_below]
|
1156
|
-
block_source = { document_filename: link_state&.document_filename }
|
1157
1177
|
|
1158
1178
|
[label_format_above && format(label_format_above,
|
1159
1179
|
block_source.merge({ block_name: selected[:oname] }))] +
|
@@ -1192,20 +1212,126 @@ module MarkdownExec
|
|
1192
1212
|
)
|
1193
1213
|
end
|
1194
1214
|
|
1215
|
+
# format + glob + select for file in load block
|
1216
|
+
# name has references to ENV vars and doc and batch vars incl. timestamp
|
1217
|
+
def load_filespec_from_expression(expression)
|
1218
|
+
# Process expression with embedded formatting
|
1219
|
+
expanded_expression = formatted_expression(expression)
|
1220
|
+
|
1221
|
+
# Handle wildcards or direct file specification
|
1222
|
+
if contains_wildcards?(expanded_expression)
|
1223
|
+
load_filespec_wildcard_expansion(expanded_expression)
|
1224
|
+
else
|
1225
|
+
expanded_expression
|
1226
|
+
end
|
1227
|
+
end
|
1228
|
+
|
1229
|
+
def save_filespec_from_expression(expression)
|
1230
|
+
# Process expression with embedded formatting
|
1231
|
+
formatted = formatted_expression(expression)
|
1232
|
+
|
1233
|
+
# Handle wildcards or direct file specification
|
1234
|
+
if contains_wildcards?(formatted)
|
1235
|
+
save_filespec_wildcard_expansion(formatted)
|
1236
|
+
else
|
1237
|
+
formatted
|
1238
|
+
end
|
1239
|
+
end
|
1240
|
+
|
1241
|
+
# private
|
1242
|
+
|
1243
|
+
# Expand expression if it contains format specifiers
|
1244
|
+
def formatted_expression(expr)
|
1245
|
+
expr.include?('%{') ? format_expression(expr) : expr
|
1246
|
+
end
|
1247
|
+
|
1248
|
+
# Format expression using environment variables and run state
|
1249
|
+
def format_expression(expr)
|
1250
|
+
data = link_load_format_data
|
1251
|
+
ENV.each { |key, value| data[key] = value }
|
1252
|
+
format(expr, data)
|
1253
|
+
end
|
1254
|
+
|
1255
|
+
# Check if the expression contains wildcard characters
|
1256
|
+
def contains_wildcards?(expr)
|
1257
|
+
expr.match(%r{\*|\?|\[})
|
1258
|
+
end
|
1259
|
+
|
1260
|
+
# Handle expression with wildcard characters
|
1261
|
+
def load_filespec_wildcard_expansion(expr)
|
1262
|
+
files = find_files(expr)
|
1263
|
+
case files.count
|
1264
|
+
when 0
|
1265
|
+
HashDelegator.error_handler("no files found with '#{expr}' ", { abort: true })
|
1266
|
+
when 1
|
1267
|
+
files.first
|
1268
|
+
else
|
1269
|
+
prompt_select_code_filename(files)
|
1270
|
+
end
|
1271
|
+
end
|
1272
|
+
|
1273
|
+
# Handle expression with wildcard characters
|
1274
|
+
# allow user to select or enter
|
1275
|
+
def puts_gets_oprompt_(filespec)
|
1276
|
+
puts format(@delegate_object[:prompt_show_expr_format],
|
1277
|
+
{ expr: filespec })
|
1278
|
+
puts @delegate_object[:prompt_enter_filespec]
|
1279
|
+
gets.chomp
|
1280
|
+
end
|
1281
|
+
|
1282
|
+
# prompt user to enter a path (i.e. containing a path separator)
|
1283
|
+
# or name to substitute into the wildcard expression
|
1284
|
+
def prompt_for_filespec_with_wildcard(filespec)
|
1285
|
+
puts format(@delegate_object[:prompt_show_expr_format],
|
1286
|
+
{ expr: filespec })
|
1287
|
+
puts @delegate_object[:prompt_enter_filespec]
|
1288
|
+
resolve_path_or_substitute(gets.chomp, filespec)
|
1289
|
+
end
|
1290
|
+
|
1291
|
+
# Handle expression with wildcard characters
|
1292
|
+
# allow user to select or enter
|
1293
|
+
def save_filespec_wildcard_expansion(filespec)
|
1294
|
+
files = find_files(filespec)
|
1295
|
+
case files.count
|
1296
|
+
when 0
|
1297
|
+
prompt_for_filespec_with_wildcard(filespec)
|
1298
|
+
else
|
1299
|
+
## user selects from existing files or other
|
1300
|
+
# input into path with wildcard for easy entry
|
1301
|
+
#
|
1302
|
+
name = prompt_select_code_filename([@delegate_object[:prompt_filespec_other]] + files)
|
1303
|
+
if name == @delegate_object[:prompt_filespec_other]
|
1304
|
+
prompt_for_filespec_with_wildcard(filespec)
|
1305
|
+
else
|
1306
|
+
name
|
1307
|
+
end
|
1308
|
+
end
|
1309
|
+
end
|
1310
|
+
|
1311
|
+
def link_load_format_data
|
1312
|
+
{
|
1313
|
+
batch_index: @run_state.batch_index,
|
1314
|
+
batch_random: @run_state.batch_random,
|
1315
|
+
block_name: @delegate_object[:block_name],
|
1316
|
+
document_filename: File.basename(@delegate_object[:filename]),
|
1317
|
+
document_filespec: @delegate_object[:filename],
|
1318
|
+
home: Dir.pwd,
|
1319
|
+
started_at: Time.now.utc.strftime(@delegate_object[:execute_command_title_time_format])
|
1320
|
+
}
|
1321
|
+
end
|
1322
|
+
|
1195
1323
|
# Loads auto blocks based on delegate object settings and updates if new filename is detected.
|
1196
1324
|
# Executes a specified block once per filename.
|
1197
1325
|
# @param all_blocks [Array] Array of all block elements.
|
1198
1326
|
# @return [Boolean, nil] True if values were modified, nil otherwise.
|
1199
1327
|
def load_auto_blocks(all_blocks)
|
1200
1328
|
block_name = @delegate_object[:document_load_opts_block_name]
|
1201
|
-
unless block_name.present? && @most_recent_loaded_filename != @delegate_object[:filename]
|
1202
|
-
return
|
1203
|
-
end
|
1329
|
+
return unless block_name.present? && @most_recent_loaded_filename != @delegate_object[:filename]
|
1204
1330
|
|
1205
1331
|
block = HashDelegator.block_find(all_blocks, :oname, block_name)
|
1206
1332
|
return unless block
|
1207
1333
|
|
1208
|
-
options_state = read_show_options_and_trigger_reuse(block)
|
1334
|
+
options_state = read_show_options_and_trigger_reuse(selected: block)
|
1209
1335
|
@menu_base_options.merge!(options_state.options)
|
1210
1336
|
@delegate_object.merge!(options_state.options)
|
1211
1337
|
|
@@ -1231,7 +1357,7 @@ module MarkdownExec
|
|
1231
1357
|
all_blocks, mdoc = mdoc_and_blocks_from_nested_files if load_auto_blocks(all_blocks)
|
1232
1358
|
|
1233
1359
|
menu_blocks = mdoc.fcbs_per_options(@delegate_object)
|
1234
|
-
add_menu_chrome_blocks!(menu_blocks, link_state)
|
1360
|
+
add_menu_chrome_blocks!(menu_blocks: menu_blocks, link_state: link_state)
|
1235
1361
|
### compress empty lines
|
1236
1362
|
HashDelegator.delete_consecutive_blank_lines!(menu_blocks) if true
|
1237
1363
|
[all_blocks, menu_blocks, mdoc]
|
@@ -1273,13 +1399,6 @@ module MarkdownExec
|
|
1273
1399
|
end
|
1274
1400
|
end
|
1275
1401
|
|
1276
|
-
def shift_cli_argument
|
1277
|
-
return true unless @menu_base_options[:input_cli_rest].present?
|
1278
|
-
|
1279
|
-
@cli_block_name = @menu_base_options[:input_cli_rest].shift
|
1280
|
-
false
|
1281
|
-
end
|
1282
|
-
|
1283
1402
|
def output_color_formatted(data_sym, color_sym)
|
1284
1403
|
formatted_string = string_send_color(@delegate_object[data_sym],
|
1285
1404
|
color_sym)
|
@@ -1502,7 +1621,7 @@ module MarkdownExec
|
|
1502
1621
|
#
|
1503
1622
|
# @return [Boolean] Returns true if the user approves (selects 'Yes'), false otherwise.
|
1504
1623
|
##
|
1505
|
-
def prompt_for_user_approval(required_lines
|
1624
|
+
def prompt_for_user_approval(required_lines:, selected:)
|
1506
1625
|
# Present a selection menu for user approval.
|
1507
1626
|
sel = @prompt.select(
|
1508
1627
|
string_send_color(@delegate_object[:prompt_approve_block],
|
@@ -1522,7 +1641,7 @@ module MarkdownExec
|
|
1522
1641
|
if sel == MenuOptions::SCRIPT_TO_CLIPBOARD
|
1523
1642
|
copy_to_clipboard(required_lines)
|
1524
1643
|
elsif sel == MenuOptions::SAVE_SCRIPT
|
1525
|
-
save_to_file(required_lines, selected)
|
1644
|
+
save_to_file(required_lines: required_lines, selected: selected)
|
1526
1645
|
end
|
1527
1646
|
|
1528
1647
|
sel == MenuOptions::YES
|
@@ -1547,6 +1666,21 @@ module MarkdownExec
|
|
1547
1666
|
|
1548
1667
|
# public
|
1549
1668
|
|
1669
|
+
def prompt_select_code_filename(filenames)
|
1670
|
+
@prompt.select(
|
1671
|
+
string_send_color(@delegate_object[:prompt_select_code_file],
|
1672
|
+
:prompt_color_after_script_execution),
|
1673
|
+
filter: true,
|
1674
|
+
quiet: true
|
1675
|
+
) do |menu|
|
1676
|
+
filenames.each do |filename|
|
1677
|
+
menu.choice filename
|
1678
|
+
end
|
1679
|
+
end
|
1680
|
+
rescue TTY::Reader::InputInterrupt
|
1681
|
+
exit 1
|
1682
|
+
end
|
1683
|
+
|
1550
1684
|
# Handles the processing of a link block in Markdown Execution.
|
1551
1685
|
# It loads YAML data from the link_block_body content, pushes the state to history,
|
1552
1686
|
# sets environment variables, and decides on the next block to load.
|
@@ -1555,18 +1689,18 @@ module MarkdownExec
|
|
1555
1689
|
# @param mdoc [Object] Markdown document object.
|
1556
1690
|
# @param selected [FCB] Selected code block.
|
1557
1691
|
# @return [LoadFileLinkState] Object indicating the next action for file loading.
|
1558
|
-
def push_link_history_and_trigger_load(link_block_body, mdoc, selected,
|
1559
|
-
link_state
|
1692
|
+
def push_link_history_and_trigger_load(link_block_body: [], mdoc: nil, selected: FCB.new,
|
1693
|
+
link_state: LinkState.new, block_source: {})
|
1560
1694
|
link_block_data = HashDelegator.parse_yaml_data_from_body(link_block_body)
|
1561
1695
|
|
1562
1696
|
## collect blocks specified by block
|
1563
1697
|
#
|
1564
1698
|
if mdoc
|
1565
1699
|
code_info = mdoc.collect_recursively_required_code(
|
1566
|
-
selected[:oname],
|
1700
|
+
anyname: selected[:oname],
|
1567
1701
|
label_format_above: @delegate_object[:shell_code_label_format_above],
|
1568
1702
|
label_format_below: @delegate_object[:shell_code_label_format_below],
|
1569
|
-
block_source:
|
1703
|
+
block_source: block_source
|
1570
1704
|
)
|
1571
1705
|
code_lines = code_info[:code]
|
1572
1706
|
block_names = code_info[:block_names]
|
@@ -1576,7 +1710,6 @@ module MarkdownExec
|
|
1576
1710
|
code_lines = []
|
1577
1711
|
dependencies = {}
|
1578
1712
|
end
|
1579
|
-
next_document_filename = link_block_data[LinkKeys::File] || @delegate_object[:filename]
|
1580
1713
|
|
1581
1714
|
# load key and values from link block into current environment
|
1582
1715
|
#
|
@@ -1584,30 +1717,25 @@ module MarkdownExec
|
|
1584
1717
|
code_lines.push "# #{selected[:oname]}"
|
1585
1718
|
(link_block_data[LinkKeys::Vars] || []).each do |(key, value)|
|
1586
1719
|
ENV[key] = value.to_s
|
1587
|
-
|
1588
|
-
code_lines.push "#{key}=\"#{Shellwords.escape(value)}\""
|
1720
|
+
code_lines.push(assign_key_value_in_bash(key, value))
|
1589
1721
|
end
|
1590
1722
|
end
|
1591
1723
|
|
1592
1724
|
## append blocks loaded, apply LinkKeys::Eval
|
1593
1725
|
#
|
1594
|
-
if (
|
1595
|
-
|
1726
|
+
if (load_expr = link_block_data.fetch(LinkKeys::Load, '')).present?
|
1727
|
+
load_filespec = load_filespec_from_expression(load_expr)
|
1728
|
+
code_lines += File.readlines(load_filespec, chomp: true) if load_filespec
|
1596
1729
|
end
|
1597
1730
|
|
1598
1731
|
# if an eval link block, evaluate code_lines and return its standard output
|
1599
1732
|
#
|
1600
1733
|
if link_block_data.fetch(LinkKeys::Eval,
|
1601
1734
|
false) || link_block_data.fetch(LinkKeys::Exec, false)
|
1602
|
-
code_lines = link_block_data_eval(link_state, code_lines, selected, link_block_data)
|
1735
|
+
code_lines = link_block_data_eval(link_state, code_lines, selected, link_block_data, block_source: block_source)
|
1603
1736
|
end
|
1604
1737
|
|
1605
|
-
|
1606
|
-
#
|
1607
|
-
if (save_filespec = link_block_data.fetch(LinkKeys::Save, '')).present?
|
1608
|
-
File.write(save_filespec, HashDelegator.join_code_lines(link_state&.inherited_lines))
|
1609
|
-
next_document_filename = @delegate_object[:filename]
|
1610
|
-
end
|
1738
|
+
next_document_filename = write_inherited_lines_to_file(link_state, link_block_data)
|
1611
1739
|
|
1612
1740
|
if link_block_data[LinkKeys::Return]
|
1613
1741
|
pop_add_current_code_to_head_and_trigger_load(link_state, block_names, code_lines,
|
@@ -1620,13 +1748,26 @@ module MarkdownExec
|
|
1620
1748
|
inherited_block_names: ((link_state&.inherited_block_names || []) + block_names).sort.uniq,
|
1621
1749
|
inherited_dependencies: (link_state&.inherited_dependencies || {}).merge(dependencies || {}), ### merge, not replace, key data
|
1622
1750
|
inherited_lines: HashDelegator.code_merge(link_state&.inherited_lines, code_lines),
|
1623
|
-
next_block_name: link_block_data.fetch(LinkKeys::NextBlock,
|
1751
|
+
next_block_name: link_block_data.fetch(LinkKeys::NextBlock,
|
1752
|
+
nil) || link_block_data[LinkKeys::Block] || '',
|
1624
1753
|
next_document_filename: next_document_filename,
|
1625
1754
|
next_load_file: next_document_filename == @delegate_object[:filename] ? LoadFile::Reuse : LoadFile::Load
|
1626
1755
|
)
|
1627
1756
|
end
|
1628
1757
|
end
|
1629
1758
|
|
1759
|
+
# Determines if a given path is absolute or substitutes a placeholder in an expression with the path.
|
1760
|
+
# @param path [String] The input path to check or fill in.
|
1761
|
+
# @param expression [String] The expression where a wildcard '*' is replaced by the path if it's not absolute.
|
1762
|
+
# @return [String] The absolute path or the expression with the wildcard replaced by the path.
|
1763
|
+
def resolve_path_or_substitute(path, expression)
|
1764
|
+
if path.include?('/')
|
1765
|
+
path
|
1766
|
+
else
|
1767
|
+
expression.gsub('*', path)
|
1768
|
+
end
|
1769
|
+
end
|
1770
|
+
|
1630
1771
|
def runtime_exception(exception_sym, name, items)
|
1631
1772
|
if @delegate_object[exception_sym] != 0
|
1632
1773
|
data = { name: name, detail: items.join(', ') }
|
@@ -1646,11 +1787,23 @@ module MarkdownExec
|
|
1646
1787
|
exit @delegate_object[exception_sym]
|
1647
1788
|
end
|
1648
1789
|
|
1649
|
-
def save_to_file(required_lines
|
1650
|
-
write_command_file(required_lines, selected)
|
1790
|
+
def save_to_file(required_lines:, selected:)
|
1791
|
+
write_command_file(required_lines: required_lines, selected: selected)
|
1651
1792
|
@fout.fout "File saved: #{@run_state.saved_filespec}"
|
1652
1793
|
end
|
1653
1794
|
|
1795
|
+
def block_state_for_name_from_cli(block_name)
|
1796
|
+
SelectedBlockMenuState.new(
|
1797
|
+
@dml_blocks_in_file.find do |item|
|
1798
|
+
item[:oname] == block_name
|
1799
|
+
end&.merge(
|
1800
|
+
block_name_from_cli: true,
|
1801
|
+
block_name_from_ui: false
|
1802
|
+
),
|
1803
|
+
MenuState::CONTINUE
|
1804
|
+
)
|
1805
|
+
end
|
1806
|
+
|
1654
1807
|
# Select and execute a code block from a Markdown document.
|
1655
1808
|
#
|
1656
1809
|
# This method allows the user to interactively select a code block from a
|
@@ -1659,57 +1812,109 @@ module MarkdownExec
|
|
1659
1812
|
# @return [Nil] Returns nil if no code block is selected or an error occurs.
|
1660
1813
|
def document_menu_loop
|
1661
1814
|
@menu_base_options = @delegate_object
|
1662
|
-
|
1815
|
+
@dml_link_state = LinkState.new(
|
1663
1816
|
block_name: @delegate_object[:block_name],
|
1664
1817
|
document_filename: @delegate_object[:filename]
|
1665
1818
|
)
|
1666
|
-
@run_state.block_name_from_cli =
|
1667
|
-
@cli_block_name =
|
1668
|
-
|
1669
|
-
|
1819
|
+
@run_state.block_name_from_cli = @dml_link_state.block_name.present?
|
1820
|
+
@cli_block_name = @dml_link_state.block_name
|
1821
|
+
@dml_now_using_cli = @run_state.block_name_from_cli
|
1822
|
+
@dml_menu_default_dname = nil
|
1823
|
+
@dml_block_state = SelectedBlockMenuState.new
|
1670
1824
|
|
1671
1825
|
@run_state.batch_random = Random.new.rand
|
1672
1826
|
@run_state.batch_index = 0
|
1673
1827
|
|
1674
|
-
|
1675
|
-
@
|
1676
|
-
@
|
1828
|
+
InputSequencer.new(
|
1829
|
+
@delegate_object[:filename],
|
1830
|
+
@delegate_object[:input_cli_rest]
|
1831
|
+
).run do |msg, data|
|
1832
|
+
case msg
|
1833
|
+
when :parse_document # once for each menu
|
1834
|
+
# puts "@ - parse document #{data}"
|
1835
|
+
ii_parse_document(data)
|
1836
|
+
|
1837
|
+
when :display_menu
|
1838
|
+
# warn "@ - display menu:"
|
1839
|
+
# ii_display_menu
|
1840
|
+
@dml_block_state = SelectedBlockMenuState.new
|
1841
|
+
@delegate_object[:block_name] = nil
|
1842
|
+
|
1843
|
+
when :user_choice
|
1844
|
+
# puts "? - Select a block to execute (or type #{$texit} to exit):"
|
1845
|
+
break if ii_user_choice == :break # into @dml_block_state
|
1846
|
+
break if @dml_block_state.block.nil? # no block matched
|
1847
|
+
|
1848
|
+
# puts "! - Executing block: #{data}"
|
1849
|
+
@dml_block_state.block[:oname]
|
1850
|
+
|
1851
|
+
when :execute_block
|
1852
|
+
block_name = data
|
1853
|
+
if block_name == '* Back' ####
|
1854
|
+
debounce_reset
|
1855
|
+
@menu_user_clicked_back_link = true
|
1856
|
+
load_file_link_state = pop_link_history_and_trigger_load
|
1857
|
+
@dml_link_state = load_file_link_state.link_state
|
1858
|
+
|
1859
|
+
InputSequencer.merge_link_state(
|
1860
|
+
@dml_link_state,
|
1861
|
+
InputSequencer.next_link_state(
|
1862
|
+
block_name: @dml_link_state.block_name,
|
1863
|
+
document_filename: @dml_link_state.document_filename,
|
1864
|
+
prior_block_was_link: true
|
1865
|
+
)
|
1866
|
+
)
|
1677
1867
|
|
1678
|
-
|
1679
|
-
|
1680
|
-
|
1868
|
+
else
|
1869
|
+
@dml_block_state = block_state_for_name_from_cli(block_name)
|
1870
|
+
if @dml_block_state.block[:shell] == BlockType::OPTS
|
1871
|
+
debounce_reset
|
1872
|
+
link_state = LinkState.new
|
1873
|
+
options_state = read_show_options_and_trigger_reuse(
|
1874
|
+
selected: @dml_block_state.block,
|
1875
|
+
link_state: link_state
|
1876
|
+
)
|
1877
|
+
|
1878
|
+
@menu_base_options.merge!(options_state.options)
|
1879
|
+
@delegate_object.merge!(options_state.options)
|
1880
|
+
options_state.load_file_link_state.link_state
|
1881
|
+
else
|
1882
|
+
ii_execute_block(block_name)
|
1681
1883
|
|
1682
|
-
|
1683
|
-
|
1684
|
-
|
1685
|
-
|
1686
|
-
# &bsp '@run_state.block_name_from_cli:',@run_state.block_name_from_cli
|
1687
|
-
if !block_state
|
1688
|
-
HashDelegator.error_handler('block_state missing', { abort: true })
|
1689
|
-
elsif block_state.state == MenuState::EXIT
|
1690
|
-
# &bsp 'load_cli_or_user_selected_block -> break'
|
1691
|
-
break
|
1692
|
-
end
|
1884
|
+
if prompt_user_exit(block_name_from_cli: @run_state.block_name_from_cli,
|
1885
|
+
selected: @dml_block_state.block)
|
1886
|
+
return :break
|
1887
|
+
end
|
1693
1888
|
|
1694
|
-
|
1695
|
-
|
1696
|
-
|
1697
|
-
|
1698
|
-
|
1699
|
-
|
1700
|
-
|
1889
|
+
## order of block name processing: link block, cli, from user
|
1890
|
+
#
|
1891
|
+
@cli_block_name = block_name
|
1892
|
+
@dml_link_state.block_name, @run_state.block_name_from_cli, cli_break = \
|
1893
|
+
HashDelegator.next_link_state(
|
1894
|
+
block_name_from_cli: !@dml_link_state.block_name,
|
1895
|
+
was_using_cli: @dml_now_using_cli,
|
1896
|
+
block_state: @dml_block_state,
|
1897
|
+
block_name: @dml_link_state.block_name
|
1898
|
+
)
|
1899
|
+
|
1900
|
+
if !@dml_block_state.block[:block_name_from_ui] && cli_break
|
1901
|
+
# &bsp '!block_name_from_ui + cli_break -> break'
|
1902
|
+
return :break
|
1903
|
+
end
|
1701
1904
|
|
1702
|
-
|
1703
|
-
|
1704
|
-
|
1705
|
-
|
1706
|
-
|
1707
|
-
|
1708
|
-
HashDelegator.next_link_state(!link_state.block_name && !shift_cli_argument, now_using_cli, block_state, block_name: link_state.block_name)
|
1905
|
+
InputSequencer.next_link_state(
|
1906
|
+
block_name: @dml_link_state.block_name,
|
1907
|
+
prior_block_was_link: @dml_block_state.block[:shell] != BlockType::BASH
|
1908
|
+
)
|
1909
|
+
end
|
1910
|
+
end
|
1709
1911
|
|
1710
|
-
|
1711
|
-
|
1712
|
-
|
1912
|
+
when :exit?
|
1913
|
+
data == $texit
|
1914
|
+
when :stay?
|
1915
|
+
data == $stay
|
1916
|
+
else
|
1917
|
+
raise "Invalid message: #{msg}"
|
1713
1918
|
end
|
1714
1919
|
end
|
1715
1920
|
rescue StandardError
|
@@ -1717,23 +1922,66 @@ module MarkdownExec
|
|
1717
1922
|
{ abort: true })
|
1718
1923
|
end
|
1719
1924
|
|
1720
|
-
def
|
1925
|
+
def ii_parse_document(_document_filename)
|
1926
|
+
@run_state.batch_index += 1
|
1927
|
+
@run_state.in_own_window = false
|
1928
|
+
|
1929
|
+
# &bsp 'loop', block_name_from_cli, @cli_block_name
|
1930
|
+
@run_state.block_name_from_cli, @dml_now_using_cli, @dml_blocks_in_file, @dml_menu_blocks, @dml_mdoc = \
|
1931
|
+
set_delobj_menu_loop_vars(block_name_from_cli: @run_state.block_name_from_cli,
|
1932
|
+
now_using_cli: @dml_now_using_cli,
|
1933
|
+
link_state: @dml_link_state)
|
1934
|
+
end
|
1935
|
+
|
1936
|
+
def ii_user_choice
|
1937
|
+
@dml_block_state = load_cli_or_user_selected_block(all_blocks: @dml_blocks_in_file,
|
1938
|
+
menu_blocks: @dml_menu_blocks,
|
1939
|
+
default: @dml_menu_default_dname)
|
1940
|
+
# &bsp '@run_state.block_name_from_cli:',@run_state.block_name_from_cli
|
1941
|
+
if !@dml_block_state
|
1942
|
+
HashDelegator.error_handler('block_state missing', { abort: true })
|
1943
|
+
elsif @dml_block_state.state == MenuState::EXIT
|
1944
|
+
# &bsp 'load_cli_or_user_selected_block -> break'
|
1945
|
+
:break
|
1946
|
+
end
|
1947
|
+
end
|
1948
|
+
|
1949
|
+
def ii_execute_block(block_name)
|
1950
|
+
@dml_block_state = block_state_for_name_from_cli(block_name)
|
1951
|
+
|
1952
|
+
dump_and_warn_block_state(selected: @dml_block_state.block)
|
1953
|
+
@dml_link_state, @dml_menu_default_dname = \
|
1954
|
+
exec_bash_next_state(
|
1955
|
+
selected: @dml_block_state.block,
|
1956
|
+
mdoc: @dml_mdoc,
|
1957
|
+
link_state: @dml_link_state,
|
1958
|
+
block_source: {
|
1959
|
+
document_filename: @delegate_object[:filename],
|
1960
|
+
time_now_date: Time.now.utc.strftime(@delegate_object[:shell_code_label_time_format])
|
1961
|
+
}
|
1962
|
+
)
|
1963
|
+
end
|
1964
|
+
|
1965
|
+
def exec_bash_next_state(selected:, mdoc:, link_state:, block_source: {})
|
1721
1966
|
lfls = execute_shell_type(
|
1722
|
-
|
1723
|
-
mdoc,
|
1724
|
-
link_state,
|
1725
|
-
block_source:
|
1967
|
+
selected: selected,
|
1968
|
+
mdoc: mdoc,
|
1969
|
+
link_state: link_state,
|
1970
|
+
block_source: block_source
|
1726
1971
|
)
|
1727
1972
|
|
1728
1973
|
# if the same menu is being displayed, collect the display name of the selected menu item for use as the default item
|
1729
1974
|
[lfls.link_state,
|
1730
|
-
lfls.load_file == LoadFile::Load ? nil :
|
1975
|
+
lfls.load_file == LoadFile::Load ? nil : selected[:dname]]
|
1731
1976
|
end
|
1732
1977
|
|
1733
|
-
def set_delobj_menu_loop_vars(block_name_from_cli
|
1978
|
+
def set_delobj_menu_loop_vars(block_name_from_cli:, now_using_cli:, link_state:)
|
1734
1979
|
block_name_from_cli, now_using_cli = \
|
1735
|
-
manage_cli_selection_state(block_name_from_cli
|
1736
|
-
|
1980
|
+
manage_cli_selection_state(block_name_from_cli: block_name_from_cli,
|
1981
|
+
now_using_cli: now_using_cli,
|
1982
|
+
link_state: link_state)
|
1983
|
+
set_delob_filename_block_name(link_state: link_state,
|
1984
|
+
block_name_from_cli: block_name_from_cli)
|
1737
1985
|
|
1738
1986
|
# update @delegate_object and @menu_base_options in auto_load
|
1739
1987
|
#
|
@@ -1745,14 +1993,14 @@ module MarkdownExec
|
|
1745
1993
|
|
1746
1994
|
# user prompt to exit if the menu will be displayed again
|
1747
1995
|
#
|
1748
|
-
def prompt_user_exit(block_name_from_cli
|
1996
|
+
def prompt_user_exit(block_name_from_cli:, selected:)
|
1749
1997
|
!block_name_from_cli &&
|
1750
|
-
|
1998
|
+
selected[:shell] == BlockType::BASH &&
|
1751
1999
|
@delegate_object[:pause_after_script_execution] &&
|
1752
2000
|
prompt_select_continue == MenuState::EXIT
|
1753
2001
|
end
|
1754
2002
|
|
1755
|
-
def manage_cli_selection_state(block_name_from_cli
|
2003
|
+
def manage_cli_selection_state(block_name_from_cli:, now_using_cli:, link_state:)
|
1756
2004
|
if block_name_from_cli && @cli_block_name == @menu_base_options[:menu_persist_block_name]
|
1757
2005
|
# &bsp 'pause cli control, allow user to select block'
|
1758
2006
|
block_name_from_cli = false
|
@@ -1775,7 +2023,7 @@ module MarkdownExec
|
|
1775
2023
|
#
|
1776
2024
|
# @param link_state [LinkState] The current link state object.
|
1777
2025
|
# @param block_name_from_cli [Boolean] Indicates if the block name is from CLI.
|
1778
|
-
def set_delob_filename_block_name(link_state
|
2026
|
+
def set_delob_filename_block_name(link_state:, block_name_from_cli:)
|
1779
2027
|
@delegate_object[:filename] = link_state.document_filename
|
1780
2028
|
link_state.block_name = @delegate_object[:block_name] =
|
1781
2029
|
block_name_from_cli ? @cli_block_name : link_state.block_name
|
@@ -1788,9 +2036,7 @@ module MarkdownExec
|
|
1788
2036
|
# @param menu_blocks [Hash] Hash of menu blocks.
|
1789
2037
|
# @param link_state [LinkState] Current state of the link.
|
1790
2038
|
def dump_delobj(blocks_in_file, menu_blocks, link_state)
|
1791
|
-
if @delegate_object[:dump_delegate_object]
|
1792
|
-
warn format_and_highlight_hash(@delegate_object, label: '@delegate_object')
|
1793
|
-
end
|
2039
|
+
warn format_and_highlight_hash(@delegate_object, label: '@delegate_object') if @delegate_object[:dump_delegate_object]
|
1794
2040
|
|
1795
2041
|
if @delegate_object[:dump_blocks_in_file]
|
1796
2042
|
warn format_and_highlight_dependencies(compact_and_index_hash(blocks_in_file),
|
@@ -1802,20 +2048,22 @@ module MarkdownExec
|
|
1802
2048
|
label: 'menu_blocks')
|
1803
2049
|
end
|
1804
2050
|
|
2051
|
+
warn format_and_highlight_lines(link_state.inherited_block_names, label: 'inherited_block_names') if @delegate_object[:dump_inherited_block_names]
|
2052
|
+
warn format_and_highlight_lines(link_state.inherited_dependencies, label: 'inherited_dependencies') if @delegate_object[:dump_inherited_dependencies]
|
1805
2053
|
return unless @delegate_object[:dump_inherited_lines]
|
1806
2054
|
|
1807
2055
|
warn format_and_highlight_lines(link_state.inherited_lines, label: 'inherited_lines')
|
1808
2056
|
end
|
1809
2057
|
|
1810
|
-
def dump_and_warn_block_state(
|
1811
|
-
if
|
2058
|
+
def dump_and_warn_block_state(selected:)
|
2059
|
+
if selected.nil?
|
1812
2060
|
Exceptions.warn_format("Block not found -- name: #{@delegate_object[:block_name]}",
|
1813
2061
|
{ abort: true })
|
1814
2062
|
end
|
1815
2063
|
|
1816
2064
|
return unless @delegate_object[:dump_selected_block]
|
1817
2065
|
|
1818
|
-
warn
|
2066
|
+
warn selected.to_yaml.sub(/^(?:---\n)?/, "Block:\n")
|
1819
2067
|
end
|
1820
2068
|
|
1821
2069
|
# Presents a TTY prompt to select an option or exit, returns metadata including option and selected
|
@@ -1950,8 +2198,12 @@ module MarkdownExec
|
|
1950
2198
|
if state[:in_fenced_block]
|
1951
2199
|
## end of code block
|
1952
2200
|
#
|
1953
|
-
HashDelegator.update_menu_attrib_yield_selected(
|
1954
|
-
|
2201
|
+
HashDelegator.update_menu_attrib_yield_selected(
|
2202
|
+
fcb: state[:fcb],
|
2203
|
+
messages: selected_messages,
|
2204
|
+
configuration: @delegate_object,
|
2205
|
+
&block
|
2206
|
+
)
|
1955
2207
|
state[:in_fenced_block] = false
|
1956
2208
|
else
|
1957
2209
|
## start of code block
|
@@ -1984,7 +2236,7 @@ module MarkdownExec
|
|
1984
2236
|
# @param selected [Hash] Selected item from the menu containing a YAML body.
|
1985
2237
|
# @param tgt2 [Hash, nil] An optional target hash to update with YAML data.
|
1986
2238
|
# @return [LoadFileLinkState] An instance indicating the next action for loading files.
|
1987
|
-
def read_show_options_and_trigger_reuse(selected
|
2239
|
+
def read_show_options_and_trigger_reuse(selected:, link_state: LinkState.new)
|
1988
2240
|
obj = {}
|
1989
2241
|
data = YAML.load(selected[:body].join("\n"))
|
1990
2242
|
(data || []).each do |key, value|
|
@@ -2038,7 +2290,7 @@ module MarkdownExec
|
|
2038
2290
|
end
|
2039
2291
|
|
2040
2292
|
# Handles the core logic for generating the command file's metadata and content.
|
2041
|
-
def write_command_file(required_lines
|
2293
|
+
def write_command_file(required_lines:, selected:)
|
2042
2294
|
return unless @delegate_object[:save_executed_script]
|
2043
2295
|
|
2044
2296
|
time_now = Time.now.utc
|
@@ -2074,25 +2326,16 @@ module MarkdownExec
|
|
2074
2326
|
HashDelegator.error_handler('write_command_file')
|
2075
2327
|
end
|
2076
2328
|
|
2077
|
-
|
2078
|
-
|
2079
|
-
|
2080
|
-
|
2081
|
-
|
2082
|
-
|
2083
|
-
|
2084
|
-
|
2085
|
-
|
2086
|
-
|
2087
|
-
)[:code]
|
2088
|
-
else
|
2089
|
-
[]
|
2090
|
-
end
|
2091
|
-
|
2092
|
-
code_blocks = (HashDelegator.read_required_blocks_from_temp_file(import_filename) +
|
2093
|
-
c1).join("\n")
|
2094
|
-
|
2095
|
-
HashDelegator.write_code_to_file(code_blocks, temp_file_path)
|
2329
|
+
def write_inherited_lines_to_file(link_state, link_block_data)
|
2330
|
+
save_expr = link_block_data.fetch(LinkKeys::Save, '')
|
2331
|
+
if save_expr.present?
|
2332
|
+
save_filespec = save_filespec_from_expression(save_expr)
|
2333
|
+
File.write(save_filespec, HashDelegator.join_code_lines(link_state&.inherited_lines))
|
2334
|
+
# TTY::File.create_file save_filespec, HahDelegator.join_code_lines(link_state&.inherited_lines), force: true
|
2335
|
+
@delegate_object[:filename]
|
2336
|
+
else
|
2337
|
+
link_block_data[LinkKeys::File] || @delegate_object[:filename]
|
2338
|
+
end
|
2096
2339
|
end
|
2097
2340
|
end
|
2098
2341
|
end
|
@@ -2105,6 +2348,11 @@ Bundler.require(:default)
|
|
2105
2348
|
require 'minitest/autorun'
|
2106
2349
|
require 'mocha/minitest'
|
2107
2350
|
|
2351
|
+
####
|
2352
|
+
require_relative 'dev/instance_method_wrapper'
|
2353
|
+
# MarkdownExec::HashDelegator.prepend(InstanceMethodWrapper)
|
2354
|
+
# MarkdownExec::HashDelegator.singleton_class.prepend(ClassMethodWrapper)
|
2355
|
+
|
2108
2356
|
module MarkdownExec
|
2109
2357
|
class TestHashDelegator < Minitest::Test
|
2110
2358
|
def setup
|
@@ -2136,14 +2384,14 @@ module MarkdownExec
|
|
2136
2384
|
# Test case for empty body
|
2137
2385
|
def test_push_link_history_and_trigger_load_with_empty_body
|
2138
2386
|
assert_equal LoadFile::Reuse,
|
2139
|
-
@hd.push_link_history_and_trigger_load
|
2387
|
+
@hd.push_link_history_and_trigger_load.load_file
|
2140
2388
|
end
|
2141
2389
|
|
2142
2390
|
# Test case for non-empty body without 'file' key
|
2143
2391
|
def test_push_link_history_and_trigger_load_without_file_key
|
2144
2392
|
body = ["vars:\n KEY: VALUE"]
|
2145
2393
|
assert_equal LoadFile::Reuse,
|
2146
|
-
@hd.push_link_history_and_trigger_load(body
|
2394
|
+
@hd.push_link_history_and_trigger_load(link_block_body: body).load_file
|
2147
2395
|
end
|
2148
2396
|
|
2149
2397
|
# Test case for non-empty body with 'file' key
|
@@ -2155,8 +2403,10 @@ module MarkdownExec
|
|
2155
2403
|
inherited_dependencies: {},
|
2156
2404
|
inherited_lines: ['# ', 'KEY="VALUE"']))
|
2157
2405
|
assert_equal expected_result,
|
2158
|
-
@hd.push_link_history_and_trigger_load(
|
2159
|
-
|
2406
|
+
@hd.push_link_history_and_trigger_load(
|
2407
|
+
link_block_body: body,
|
2408
|
+
selected: FCB.new(block_name: 'sample_block', filename: 'sample_file')
|
2409
|
+
)
|
2160
2410
|
end
|
2161
2411
|
|
2162
2412
|
def test_indent_all_lines_with_indent
|
@@ -2234,7 +2484,7 @@ module MarkdownExec
|
|
2234
2484
|
|
2235
2485
|
def test_append_divider_initial
|
2236
2486
|
menu_blocks = []
|
2237
|
-
@hd.append_divider(menu_blocks, :initial)
|
2487
|
+
@hd.append_divider(menu_blocks: menu_blocks, position: :initial)
|
2238
2488
|
|
2239
2489
|
assert_equal 1, menu_blocks.size
|
2240
2490
|
assert_equal 'Formatted Divider', menu_blocks.first.dname
|
@@ -2242,7 +2492,7 @@ module MarkdownExec
|
|
2242
2492
|
|
2243
2493
|
def test_append_divider_final
|
2244
2494
|
menu_blocks = []
|
2245
|
-
@hd.append_divider(menu_blocks, :final)
|
2495
|
+
@hd.append_divider(menu_blocks: menu_blocks, position: :final)
|
2246
2496
|
|
2247
2497
|
assert_equal 1, menu_blocks.size
|
2248
2498
|
assert_equal 'Formatted Divider', menu_blocks.last.dname
|
@@ -2251,7 +2501,7 @@ module MarkdownExec
|
|
2251
2501
|
def test_append_divider_without_format
|
2252
2502
|
@hd.instance_variable_set(:@delegate_object, {})
|
2253
2503
|
menu_blocks = []
|
2254
|
-
@hd.append_divider(menu_blocks, :initial)
|
2504
|
+
@hd.append_divider(menu_blocks: menu_blocks, position: :initial)
|
2255
2505
|
|
2256
2506
|
assert_empty menu_blocks
|
2257
2507
|
end
|
@@ -2322,7 +2572,7 @@ module MarkdownExec
|
|
2322
2572
|
def test_collect_required_code_lines_with_vars
|
2323
2573
|
YAML.stubs(:load).returns({ 'key' => 'value' })
|
2324
2574
|
@mdoc.stubs(:collect_recursively_required_code).returns({ code: ['code line'] })
|
2325
|
-
result = @hd.collect_required_code_lines(@mdoc, @selected, block_source: {})
|
2575
|
+
result = @hd.collect_required_code_lines(mdoc: @mdoc, selected: @selected, block_source: {})
|
2326
2576
|
|
2327
2577
|
assert_equal ['code line', 'key="value"'], result
|
2328
2578
|
end
|
@@ -2341,7 +2591,7 @@ module MarkdownExec
|
|
2341
2591
|
@hd.instance_variable_set(:@delegate_object,
|
2342
2592
|
{ block_name: 'block1' })
|
2343
2593
|
|
2344
|
-
result = @hd.load_cli_or_user_selected_block(all_blocks
|
2594
|
+
result = @hd.load_cli_or_user_selected_block(all_blocks: all_blocks)
|
2345
2595
|
|
2346
2596
|
assert_equal all_blocks.first.merge(block_name_from_ui: false), result.block
|
2347
2597
|
assert_nil result.state
|
@@ -2352,7 +2602,7 @@ module MarkdownExec
|
|
2352
2602
|
:some_state)
|
2353
2603
|
@hd.stubs(:wait_for_user_selected_block).returns(block_state)
|
2354
2604
|
|
2355
|
-
result = @hd.load_cli_or_user_selected_block
|
2605
|
+
result = @hd.load_cli_or_user_selected_block
|
2356
2606
|
|
2357
2607
|
assert_equal block_state.block.merge(block_name_from_ui: true), result.block
|
2358
2608
|
assert_equal :some_state, result.state
|
@@ -2479,7 +2729,7 @@ module MarkdownExec
|
|
2479
2729
|
@hd.instance_variable_get(:@delegate_object).stubs(:[]).with(:script_preview_tail).returns('Footer')
|
2480
2730
|
@hd.instance_variable_get(:@fout).expects(:fout).times(4)
|
2481
2731
|
|
2482
|
-
@hd.display_required_code(required_lines)
|
2732
|
+
@hd.display_required_code(required_lines: required_lines)
|
2483
2733
|
|
2484
2734
|
# Verifying that fout is called for each line and for header & footer
|
2485
2735
|
assert true # Placeholder for actual test assertions
|
@@ -2689,7 +2939,7 @@ module MarkdownExec
|
|
2689
2939
|
stream = StringIO.new("line 1\nline 2\n")
|
2690
2940
|
file_type = :stdout
|
2691
2941
|
|
2692
|
-
Thread.new { @hd.handle_stream(stream, file_type) }
|
2942
|
+
Thread.new { @hd.handle_stream(stream: stream, file_type: file_type) }
|
2693
2943
|
|
2694
2944
|
@hd.wait_for_stream_processing
|
2695
2945
|
|
@@ -2702,7 +2952,7 @@ module MarkdownExec
|
|
2702
2952
|
file_type = :stdout
|
2703
2953
|
stream.stubs(:each_line).raises(IOError)
|
2704
2954
|
|
2705
|
-
Thread.new { @hd.handle_stream(stream, file_type) }
|
2955
|
+
Thread.new { @hd.handle_stream(stream: stream, file_type: file_type) }
|
2706
2956
|
|
2707
2957
|
@hd.wait_for_stream_processing
|
2708
2958
|
|
@@ -2866,13 +3116,13 @@ module MarkdownExec
|
|
2866
3116
|
HashDelegator.expects(:default_block_title_from_body).with(@fcb)
|
2867
3117
|
Filter.expects(:yield_to_block_if_applicable).with(@fcb, [:some_message], {})
|
2868
3118
|
|
2869
|
-
HashDelegator.update_menu_attrib_yield_selected(@fcb, [:some_message])
|
3119
|
+
HashDelegator.update_menu_attrib_yield_selected(fcb: @fcb, messages: [:some_message])
|
2870
3120
|
end
|
2871
3121
|
|
2872
3122
|
def test_update_menu_attrib_yield_selected_without_body
|
2873
3123
|
@fcb.stubs(:body).returns(nil)
|
2874
3124
|
HashDelegator.expects(:initialize_fcb_names).with(@fcb)
|
2875
|
-
HashDelegator.update_menu_attrib_yield_selected(@fcb, [:some_message])
|
3125
|
+
HashDelegator.update_menu_attrib_yield_selected(fcb: @fcb, messages: [:some_message])
|
2876
3126
|
end
|
2877
3127
|
end
|
2878
3128
|
|
@@ -2943,4 +3193,30 @@ module MarkdownExec
|
|
2943
3193
|
refute block_called
|
2944
3194
|
end
|
2945
3195
|
end
|
3196
|
+
|
3197
|
+
def test_resolves_absolute_path
|
3198
|
+
absolute_path = '/usr/local/bin'
|
3199
|
+
assert_equal '/usr/local/bin', resolve_path_or_substitute(absolute_path, 'prefix/*/suffix')
|
3200
|
+
end
|
3201
|
+
|
3202
|
+
def test_substitutes_wildcard_with_path
|
3203
|
+
path = 'bin'
|
3204
|
+
expression = 'prefix/*/suffix'
|
3205
|
+
expected_result = 'prefix/bin/suffix'
|
3206
|
+
assert_equal expected_result, resolve_path_or_substitute(path, expression)
|
3207
|
+
end
|
3208
|
+
|
3209
|
+
def test_handles_path_with_no_separator_as_is
|
3210
|
+
path = 'bin'
|
3211
|
+
expression = 'prefix*suffix'
|
3212
|
+
expected_result = 'prefixbinsuffix'
|
3213
|
+
assert_equal expected_result, resolve_path_or_substitute(path, expression)
|
3214
|
+
end
|
3215
|
+
|
3216
|
+
def test_returns_expression_unchanged_for_empty_path
|
3217
|
+
path = ''
|
3218
|
+
expression = 'prefix/*/suffix'
|
3219
|
+
expected_result = 'prefix/*/suffix'
|
3220
|
+
assert_equal expected_result, resolve_path_or_substitute(path, expression)
|
3221
|
+
end
|
2946
3222
|
end # module MarkdownExec
|