markdown_exec 2.0.6 → 2.0.8
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 +10 -2
- data/CHANGELOG.md +40 -0
- data/Gemfile.lock +1 -1
- data/Rakefile +1 -0
- data/bin/tab_completion.sh +2 -2
- data/examples/interrupt.md +9 -0
- data/examples/line-wrapping.md +17 -0
- data/examples/nickname.md +46 -9
- data/examples/pause-after-execution.md +9 -0
- data/lib/colorize.rb +25 -0
- data/lib/constants.rb +1 -0
- data/lib/fcb.rb +15 -0
- data/lib/hash_delegator.rb +390 -100
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/markdown_exec.rb +1 -0
- data/lib/mdoc.rb +9 -14
- data/lib/menu.src.yml +45 -13
- data/lib/menu.yml +32 -14
- data/lib/resize_terminal.rb +215 -0
- data/lib/saved_assets.rb +4 -3
- metadata +6 -2
data/lib/hash_delegator.rb
CHANGED
@@ -6,6 +6,7 @@
|
|
6
6
|
require 'clipboard'
|
7
7
|
require 'English'
|
8
8
|
require 'fileutils'
|
9
|
+
require 'io/console'
|
9
10
|
require 'open3'
|
10
11
|
require 'optparse'
|
11
12
|
require 'ostruct'
|
@@ -31,6 +32,7 @@ require_relative 'hash'
|
|
31
32
|
require_relative 'link_history'
|
32
33
|
require_relative 'mdoc'
|
33
34
|
require_relative 'regexp'
|
35
|
+
require_relative 'resize_terminal'
|
34
36
|
require_relative 'std_out_err_logger'
|
35
37
|
require_relative 'string_util'
|
36
38
|
|
@@ -237,6 +239,22 @@ module HashDelegatorSelf
|
|
237
239
|
# @param str [String] The string to be evaluated.
|
238
240
|
# @return [Object] The result of evaluating the string.
|
239
241
|
def safeval(str)
|
242
|
+
# # Restricting to evaluate only expressions
|
243
|
+
# unless str.match?(/\A\s*\w+\s*[\+\-\*\/\=\%\&\|\<\>\!]+\s*\w+\s*\z/)
|
244
|
+
# error_handler('safeval') # 'Invalid expression'
|
245
|
+
# return
|
246
|
+
# end
|
247
|
+
|
248
|
+
# # Whitelisting allowed operations
|
249
|
+
# allowed_methods = %w[+ - * / == != < > <= >= && || % & |]
|
250
|
+
# unless allowed_methods.any? { |op| str.include?(op) }
|
251
|
+
# error_handler('safeval', 'Operation not allowed')
|
252
|
+
# return
|
253
|
+
# end
|
254
|
+
|
255
|
+
# # Sanitize input (example: removing potentially harmful characters)
|
256
|
+
# str = str.gsub(/[^0-9\+\-\*\/\(\)\<\>\!\=\%\&\|]/, '')
|
257
|
+
# Evaluate the sanitized string
|
240
258
|
result = nil
|
241
259
|
binding.eval("result = #{str}")
|
242
260
|
|
@@ -248,6 +266,16 @@ module HashDelegatorSelf
|
|
248
266
|
exit 1
|
249
267
|
end
|
250
268
|
|
269
|
+
# # Evaluates the given string as Ruby code and rescues any StandardErrors.
|
270
|
+
# # If an error occurs, it calls the error_handler method with 'safeval'.
|
271
|
+
# # @param str [String] The string to be evaluated.
|
272
|
+
# # @return [Object] The result of evaluating the string.
|
273
|
+
# def safeval(str)
|
274
|
+
# eval(str)
|
275
|
+
# rescue StandardError # catches NameError, StandardError
|
276
|
+
# error_handler('safeval')
|
277
|
+
# end
|
278
|
+
|
251
279
|
def set_file_permissions(file_path, chmod_value)
|
252
280
|
File.chmod(chmod_value, file_path)
|
253
281
|
end
|
@@ -388,6 +416,74 @@ class BashCommentFormatter
|
|
388
416
|
# end
|
389
417
|
end
|
390
418
|
|
419
|
+
class StringWrapper
|
420
|
+
attr_reader :width, :left_margin, :right_margin, :indent, :fill_margin
|
421
|
+
|
422
|
+
# Initializes the StringWrapper with the given options.
|
423
|
+
#
|
424
|
+
# @param width [Integer] the maximum width of each line
|
425
|
+
# @param left_margin [Integer] the number of spaces for the left margin
|
426
|
+
# @param right_margin [Integer] the number of spaces for the right margin
|
427
|
+
# @param indent [Integer] the number of spaces to indent all but the first line
|
428
|
+
# @param fill_margin [Boolean] whether to fill the left margin with spaces
|
429
|
+
def initialize(
|
430
|
+
width:,
|
431
|
+
fill_margin: false,
|
432
|
+
first_indent: '',
|
433
|
+
indent_space: ' ',
|
434
|
+
left_margin: 0,
|
435
|
+
margin_char: ' ',
|
436
|
+
rest_indent: '',
|
437
|
+
right_margin: 0
|
438
|
+
)
|
439
|
+
@fill_margin = fill_margin
|
440
|
+
@first_indent = first_indent
|
441
|
+
@indent = indent
|
442
|
+
@indent_space = indent_space
|
443
|
+
@rest_indent = rest_indent
|
444
|
+
@right_margin = right_margin
|
445
|
+
@width = width
|
446
|
+
|
447
|
+
@margin_space = fill_margin ? (margin_char * left_margin) : ''
|
448
|
+
@left_margin = @margin_space.length
|
449
|
+
end
|
450
|
+
|
451
|
+
# Wraps the given text according to the specified options.
|
452
|
+
#
|
453
|
+
# @param text [String] the text to wrap
|
454
|
+
# @return [String] the wrapped text
|
455
|
+
def wrap(text)
|
456
|
+
text = text.dup if text.frozen?
|
457
|
+
max_line_length = width - left_margin - right_margin - @indent_space.length
|
458
|
+
lines = []
|
459
|
+
current_line = String.new
|
460
|
+
|
461
|
+
words = text.split
|
462
|
+
words.each.with_index do |word, index|
|
463
|
+
trial_length = word.length
|
464
|
+
trial_length += @first_indent.length if index.zero?
|
465
|
+
trial_length += current_line.length + 1 + @rest_indent.length if index != 0
|
466
|
+
if trial_length > max_line_length && (words.count != 0)
|
467
|
+
lines << current_line
|
468
|
+
current_line = word
|
469
|
+
current_line = current_line.dup if current_line.frozen?
|
470
|
+
else
|
471
|
+
current_line << ' ' unless current_line.empty?
|
472
|
+
current_line << word
|
473
|
+
end
|
474
|
+
end
|
475
|
+
lines << current_line unless current_line.empty?
|
476
|
+
|
477
|
+
lines.map.with_index do |line, index|
|
478
|
+
@margin_space + if index.zero?
|
479
|
+
@first_indent
|
480
|
+
else
|
481
|
+
@rest_indent
|
482
|
+
end + line
|
483
|
+
end
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
391
487
|
module MarkdownExec
|
392
488
|
class DebugHelper
|
393
489
|
# Class-level variable to store history of printed messages
|
@@ -497,6 +593,9 @@ module MarkdownExec
|
|
497
593
|
when MenuState::SAVE
|
498
594
|
option_name = @delegate_object[:menu_option_save_name]
|
499
595
|
insert_at_top = @delegate_object[:menu_load_at_top]
|
596
|
+
when MenuState::SHELL
|
597
|
+
option_name = @delegate_object[:menu_option_shell_name]
|
598
|
+
insert_at_top = @delegate_object[:menu_load_at_top]
|
500
599
|
when MenuState::VIEW
|
501
600
|
option_name = @delegate_object[:menu_option_view_name]
|
502
601
|
insert_at_top = @delegate_object[:menu_load_at_top]
|
@@ -595,6 +694,8 @@ module MarkdownExec
|
|
595
694
|
#
|
596
695
|
# @return [Array<FCB>] An array of FCB objects representing the blocks.
|
597
696
|
def blocks_from_nested_files
|
697
|
+
register_console_attributes(@delegate_object)
|
698
|
+
|
598
699
|
blocks = []
|
599
700
|
iter_blocks_from_nested_files do |btype, fcb|
|
600
701
|
process_block_based_on_type(blocks, btype, fcb)
|
@@ -605,10 +706,12 @@ module MarkdownExec
|
|
605
706
|
HashDelegator.error_handler('blocks_from_nested_files')
|
606
707
|
end
|
607
708
|
|
709
|
+
# find a block by its original (undecorated) name or nickname (not visible in menu)
|
710
|
+
# if matched, the block returned has properties that it is from cli and not ui
|
608
711
|
def block_state_for_name_from_cli(block_name)
|
609
712
|
SelectedBlockMenuState.new(
|
610
713
|
@dml_blocks_in_file.find do |item|
|
611
|
-
|
714
|
+
block_name == item.pub_name
|
612
715
|
end&.merge(
|
613
716
|
block_name_from_cli: true,
|
614
717
|
block_name_from_ui: false
|
@@ -663,7 +766,7 @@ module MarkdownExec
|
|
663
766
|
# @return [Array<String>] Required code blocks as an array of lines.
|
664
767
|
def collect_required_code_lines(mdoc:, selected:, block_source:, link_state: LinkState.new)
|
665
768
|
required = mdoc.collect_recursively_required_code(
|
666
|
-
anyname: selected
|
769
|
+
anyname: selected.pub_name,
|
667
770
|
label_format_above: @delegate_object[:shell_code_label_format_above],
|
668
771
|
label_format_below: @delegate_object[:shell_code_label_format_below],
|
669
772
|
block_source: block_source
|
@@ -701,47 +804,15 @@ module MarkdownExec
|
|
701
804
|
system(
|
702
805
|
format(
|
703
806
|
@delegate_object[:execute_command_format],
|
704
|
-
|
705
|
-
batch_index: @run_state.batch_index,
|
706
|
-
batch_random: @run_state.batch_random,
|
707
|
-
block_name: @delegate_object[:block_name],
|
708
|
-
document_filename: File.basename(@delegate_object[:filename]),
|
709
|
-
document_filespec: @delegate_object[:filename],
|
710
|
-
home: Dir.pwd,
|
711
|
-
output_filename: File.basename(@delegate_object[:logged_stdout_filespec]),
|
712
|
-
output_filespec: @delegate_object[:logged_stdout_filespec],
|
713
|
-
script_filename: @run_state.saved_filespec,
|
714
|
-
script_filespec: File.join(Dir.pwd, @run_state.saved_filespec),
|
715
|
-
started_at: @run_state.started_at.strftime(
|
716
|
-
@delegate_object[:execute_command_title_time_format]
|
717
|
-
)
|
718
|
-
}
|
807
|
+
command_execute_in_own_window_format_arguments
|
719
808
|
)
|
720
809
|
)
|
721
810
|
|
722
811
|
else
|
723
812
|
@run_state.in_own_window = false
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
*args) do |stdin, stdout, stderr, exec_thr|
|
728
|
-
handle_stream(stream: stdout, file_type: ExecutionStreams::STD_OUT) do |line|
|
729
|
-
yield nil, line, nil, exec_thr if block_given?
|
730
|
-
end
|
731
|
-
handle_stream(stream: stderr, file_type: ExecutionStreams::STD_ERR) do |line|
|
732
|
-
yield nil, nil, line, exec_thr if block_given?
|
733
|
-
end
|
734
|
-
|
735
|
-
in_thr = handle_stream(stream: $stdin, file_type: ExecutionStreams::STD_IN) do |line|
|
736
|
-
stdin.puts(line)
|
737
|
-
yield line, nil, nil, exec_thr if block_given?
|
738
|
-
end
|
739
|
-
|
740
|
-
wait_for_stream_processing
|
741
|
-
exec_thr.join
|
742
|
-
sleep 0.1
|
743
|
-
in_thr.kill if in_thr&.alive?
|
744
|
-
end
|
813
|
+
execute_command_with_streams(
|
814
|
+
[@delegate_object[:shell], '-c', command, @delegate_object[:filename], *args]
|
815
|
+
)
|
745
816
|
end
|
746
817
|
|
747
818
|
@run_state.completed_at = Time.now.utc
|
@@ -761,6 +832,24 @@ module MarkdownExec
|
|
761
832
|
@fout.fout "Error ENOENT: #{err.inspect}"
|
762
833
|
end
|
763
834
|
|
835
|
+
def command_execute_in_own_window_format_arguments(home: Dir.pwd)
|
836
|
+
{
|
837
|
+
batch_index: @run_state.batch_index,
|
838
|
+
batch_random: @run_state.batch_random,
|
839
|
+
block_name: @delegate_object[:block_name],
|
840
|
+
document_filename: File.basename(@delegate_object[:filename]),
|
841
|
+
document_filespec: @delegate_object[:filename],
|
842
|
+
home: home,
|
843
|
+
output_filename: File.basename(@delegate_object[:logged_stdout_filespec]),
|
844
|
+
output_filespec: @delegate_object[:logged_stdout_filespec],
|
845
|
+
script_filename: @run_state.saved_filespec,
|
846
|
+
script_filespec: File.join(home, @run_state.saved_filespec),
|
847
|
+
started_at: @run_state.started_at.strftime(
|
848
|
+
@delegate_object[:execute_command_title_time_format]
|
849
|
+
)
|
850
|
+
}
|
851
|
+
end
|
852
|
+
|
764
853
|
# This method is responsible for handling the execution of generic blocks in a markdown document.
|
765
854
|
# It collects the required code lines from the document and, depending on the configuration,
|
766
855
|
# may display the code for user approval before execution. It then executes the approved block.
|
@@ -816,16 +905,70 @@ module MarkdownExec
|
|
816
905
|
# @param match_data [MatchData] The match data containing named captures for formatting.
|
817
906
|
# @param format_option [String] The format string to be used for the new block.
|
818
907
|
# @param color_method [Symbol] The color method to apply to the block's display name.
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
908
|
+
# return number of lines added
|
909
|
+
def create_and_add_chrome_block(blocks:, match_data:,
|
910
|
+
format_option:, color_method:,
|
911
|
+
case_conversion: nil,
|
912
|
+
center: nil,
|
913
|
+
wrap: nil)
|
914
|
+
line_cap = match_data.named_captures.transform_keys(&:to_sym)
|
915
|
+
|
916
|
+
# replace tabs in indent
|
917
|
+
line_cap[:indent] ||= ''
|
918
|
+
line_cap[:indent] = line_cap[:indent].dup if line_cap[:indent].frozen?
|
919
|
+
line_cap[:indent].gsub!("\t", ' ')
|
920
|
+
# replace tabs in text
|
921
|
+
line_cap[:text] ||= ''
|
922
|
+
line_cap[:text] = line_cap[:text].dup if line_cap[:text].frozen?
|
923
|
+
line_cap[:text].gsub!("\t", ' ')
|
924
|
+
# missing capture
|
925
|
+
line_cap[:line] ||= ''
|
926
|
+
|
927
|
+
accepted_width = @delegate_object[:console_width] - 2
|
928
|
+
line_caps = if wrap
|
929
|
+
if line_cap[:text].length > accepted_width
|
930
|
+
wrapper = StringWrapper.new(width: accepted_width - line_cap[:indent].length)
|
931
|
+
wrapper.wrap(line_cap[:text]).map do |line|
|
932
|
+
line_cap.dup.merge(text: line)
|
933
|
+
end
|
934
|
+
else
|
935
|
+
[line_cap]
|
936
|
+
end
|
937
|
+
else
|
938
|
+
[line_cap]
|
939
|
+
end
|
940
|
+
if center
|
941
|
+
line_caps.each do |line_obj|
|
942
|
+
line_obj[:indent] = if line_obj[:text].length < accepted_width
|
943
|
+
' ' * ((accepted_width - line_obj[:text].length) / 2)
|
944
|
+
else
|
945
|
+
''
|
946
|
+
end
|
947
|
+
end
|
948
|
+
end
|
949
|
+
|
950
|
+
line_caps.each do |line_obj|
|
951
|
+
next if line_obj[:text].nil?
|
952
|
+
|
953
|
+
case case_conversion
|
954
|
+
when :upcase
|
955
|
+
line_obj[:text].upcase!
|
956
|
+
when :downcase
|
957
|
+
line_obj[:text].downcase!
|
958
|
+
end
|
959
|
+
|
960
|
+
# format expects :line to be text only
|
961
|
+
line_obj[:line] = line_obj[:text]
|
962
|
+
oname = format(format_option, line_obj)
|
963
|
+
line_obj[:line] = line_obj[:indent] + line_obj[:text]
|
964
|
+
blocks.push FCB.new(
|
965
|
+
chrome: true,
|
966
|
+
disabled: '',
|
967
|
+
dname: line_obj[:indent] + oname.send(color_method),
|
968
|
+
oname: line_obj[:text]
|
969
|
+
)
|
970
|
+
end
|
971
|
+
line_caps.count
|
829
972
|
end
|
830
973
|
|
831
974
|
##
|
@@ -836,12 +979,12 @@ module MarkdownExec
|
|
836
979
|
# @param use_chrome [Boolean] Indicates if the chrome styling should be applied.
|
837
980
|
def create_and_add_chrome_blocks(blocks, fcb)
|
838
981
|
match_criteria = [
|
839
|
-
{ color: :menu_heading1_color, format: :menu_heading1_format, match: :heading1_match },
|
840
|
-
{ color: :menu_heading2_color, format: :menu_heading2_format, match: :heading2_match },
|
841
|
-
{ color: :menu_heading3_color, format: :menu_heading3_format, match: :heading3_match },
|
982
|
+
{ color: :menu_heading1_color, format: :menu_heading1_format, match: :heading1_match, center: true, case_conversion: :upcase, wrap: true },
|
983
|
+
{ color: :menu_heading2_color, format: :menu_heading2_format, match: :heading2_match, center: true, wrap: true },
|
984
|
+
{ color: :menu_heading3_color, format: :menu_heading3_format, match: :heading3_match, center: true, case_conversion: :downcase, wrap: true },
|
842
985
|
{ color: :menu_divider_color, format: :menu_divider_format, match: :menu_divider_match },
|
843
|
-
{ color: :menu_note_color, format: :menu_note_format, match: :menu_note_match },
|
844
|
-
{ color: :menu_task_color, format: :menu_task_format, match: :menu_task_match }
|
986
|
+
{ color: :menu_note_color, format: :menu_note_format, match: :menu_note_match, wrap: true },
|
987
|
+
{ color: :menu_task_color, format: :menu_task_format, match: :menu_task_match, wrap: true }
|
845
988
|
]
|
846
989
|
# rubocop:enable Style/UnlessElse
|
847
990
|
match_criteria.each do |criteria|
|
@@ -852,9 +995,12 @@ module MarkdownExec
|
|
852
995
|
|
853
996
|
create_and_add_chrome_block(
|
854
997
|
blocks: blocks,
|
855
|
-
|
998
|
+
case_conversion: criteria[:case_conversion],
|
999
|
+
center: criteria[:center],
|
1000
|
+
color_method: @delegate_object[criteria[:color]].to_sym,
|
856
1001
|
format_option: @delegate_object[criteria[:format]],
|
857
|
-
|
1002
|
+
match_data: mbody,
|
1003
|
+
wrap: criteria[:wrap]
|
858
1004
|
)
|
859
1005
|
break
|
860
1006
|
end
|
@@ -950,6 +1096,7 @@ module MarkdownExec
|
|
950
1096
|
block_name: @delegate_object[:block_name],
|
951
1097
|
document_filename: @delegate_object[:filename]
|
952
1098
|
)
|
1099
|
+
# @dml_link_state_block_name_from_cli = @dml_link_state.block_name.present? ###
|
953
1100
|
@run_state.block_name_from_cli = @dml_link_state.block_name.present?
|
954
1101
|
@cli_block_name = @dml_link_state.block_name
|
955
1102
|
@dml_now_using_cli = @run_state.block_name_from_cli
|
@@ -975,6 +1122,7 @@ module MarkdownExec
|
|
975
1122
|
item_edit = format(@delegate_object[:menu_link_format], HashDelegator.safeval(@delegate_object[:menu_option_edit_name]))
|
976
1123
|
item_load = format(@delegate_object[:menu_link_format], HashDelegator.safeval(@delegate_object[:menu_option_load_name]))
|
977
1124
|
item_save = format(@delegate_object[:menu_link_format], HashDelegator.safeval(@delegate_object[:menu_option_save_name]))
|
1125
|
+
item_shell = format(@delegate_object[:menu_link_format], HashDelegator.safeval(@delegate_object[:menu_option_shell_name]))
|
978
1126
|
item_view = format(@delegate_object[:menu_link_format], HashDelegator.safeval(@delegate_object[:menu_option_view_name]))
|
979
1127
|
|
980
1128
|
@run_state.batch_random = Random.new.rand
|
@@ -999,10 +1147,11 @@ module MarkdownExec
|
|
999
1147
|
|
1000
1148
|
# add menu items (glob, load, save) and enable selectively
|
1001
1149
|
menu_add_disabled_option(sf) if files.count.positive? || lines_count.positive?
|
1002
|
-
menu_enable_option(format(@delegate_object[:menu_link_format], HashDelegator.safeval(@delegate_object[:menu_option_load_name])), files.count, 'files', menu_state: MenuState::LOAD)
|
1003
|
-
menu_enable_option(format(@delegate_object[:menu_link_format], HashDelegator.safeval(@delegate_object[:menu_option_edit_name])), lines_count, 'lines', menu_state: MenuState::EDIT)
|
1004
|
-
menu_enable_option(format(@delegate_object[:menu_link_format], HashDelegator.safeval(@delegate_object[:menu_option_save_name])),
|
1005
|
-
menu_enable_option(format(@delegate_object[:menu_link_format], HashDelegator.safeval(@delegate_object[:menu_option_view_name])),
|
1150
|
+
menu_enable_option(format(@delegate_object[:menu_link_format], HashDelegator.safeval(@delegate_object[:menu_option_load_name])), files.count, 'files', menu_state: MenuState::LOAD) if files.count.positive?
|
1151
|
+
menu_enable_option(format(@delegate_object[:menu_link_format], HashDelegator.safeval(@delegate_object[:menu_option_edit_name])), lines_count, 'lines', menu_state: MenuState::EDIT) if lines_count.positive?
|
1152
|
+
menu_enable_option(format(@delegate_object[:menu_link_format], HashDelegator.safeval(@delegate_object[:menu_option_save_name])), 1, '', menu_state: MenuState::SAVE) if lines_count.positive?
|
1153
|
+
menu_enable_option(format(@delegate_object[:menu_link_format], HashDelegator.safeval(@delegate_object[:menu_option_view_name])), 1, '', menu_state: MenuState::VIEW) if lines_count.positive?
|
1154
|
+
menu_enable_option(format(@delegate_object[:menu_link_format], HashDelegator.safeval(@delegate_object[:menu_option_shell_name])), 1, '', menu_state: MenuState::SHELL) if @delegate_object[:menu_with_shell]
|
1006
1155
|
end
|
1007
1156
|
|
1008
1157
|
when :display_menu
|
@@ -1015,7 +1164,7 @@ module MarkdownExec
|
|
1015
1164
|
if @dml_link_state.block_name.present?
|
1016
1165
|
# @prior_block_was_link = true
|
1017
1166
|
@dml_block_state.block = @dml_blocks_in_file.find do |item|
|
1018
|
-
item
|
1167
|
+
item.pub_name == @dml_link_state.block_name
|
1019
1168
|
end
|
1020
1169
|
@dml_link_state.block_name = nil
|
1021
1170
|
else
|
@@ -1024,8 +1173,7 @@ module MarkdownExec
|
|
1024
1173
|
break if @dml_block_state.block.nil? # no block matched
|
1025
1174
|
end
|
1026
1175
|
# puts "! - Executing block: #{data}"
|
1027
|
-
|
1028
|
-
@dml_block_state.block&.fetch(:oname, nil)
|
1176
|
+
@dml_block_state.block&.pub_name
|
1029
1177
|
|
1030
1178
|
when :execute_block
|
1031
1179
|
case (block_name = data)
|
@@ -1045,6 +1193,7 @@ module MarkdownExec
|
|
1045
1193
|
)
|
1046
1194
|
|
1047
1195
|
when item_edit
|
1196
|
+
debounce_reset
|
1048
1197
|
edited = edit_text(@dml_link_state.inherited_lines.join("\n"))
|
1049
1198
|
@dml_link_state.inherited_lines = edited.split("\n") if edited
|
1050
1199
|
InputSequencer.next_link_state(prior_block_was_link: true)
|
@@ -1070,10 +1219,28 @@ module MarkdownExec
|
|
1070
1219
|
return :break
|
1071
1220
|
|
1072
1221
|
end
|
1222
|
+
InputSequencer.next_link_state(prior_block_was_link: true)
|
1223
|
+
|
1224
|
+
when item_shell
|
1225
|
+
debounce_reset
|
1226
|
+
loop do
|
1227
|
+
command = prompt_for_command(":MDE #{Time.now.strftime('%FT%TZ')}> ".bgreen)
|
1228
|
+
break if !command.present? || command == 'exit'
|
1073
1229
|
|
1230
|
+
exit_status = execute_command_with_streams(
|
1231
|
+
[@delegate_object[:shell], '-c', command]
|
1232
|
+
)
|
1233
|
+
case exit_status
|
1234
|
+
when 0
|
1235
|
+
warn "#{'OK'.green} #{exit_status}"
|
1236
|
+
else
|
1237
|
+
warn "#{'ERR'.bred} #{exit_status}"
|
1238
|
+
end
|
1239
|
+
end
|
1074
1240
|
InputSequencer.next_link_state(prior_block_was_link: true)
|
1075
1241
|
|
1076
1242
|
when item_view
|
1243
|
+
debounce_reset
|
1077
1244
|
warn @dml_link_state.inherited_lines.join("\n")
|
1078
1245
|
InputSequencer.next_link_state(prior_block_was_link: true)
|
1079
1246
|
|
@@ -1103,7 +1270,7 @@ module MarkdownExec
|
|
1103
1270
|
@dml_link_state.block_name, @run_state.block_name_from_cli, cli_break = \
|
1104
1271
|
HashDelegator.next_link_state(
|
1105
1272
|
block_name: @dml_link_state.block_name,
|
1106
|
-
block_name_from_cli:
|
1273
|
+
block_name_from_cli: @dml_now_using_cli,
|
1107
1274
|
block_state: @dml_block_state,
|
1108
1275
|
was_using_cli: @dml_now_using_cli
|
1109
1276
|
)
|
@@ -1244,6 +1411,53 @@ module MarkdownExec
|
|
1244
1411
|
#.tap { |ret| pp [__FILE__,__LINE__,'exec_bash_next_state()',ret] }
|
1245
1412
|
end
|
1246
1413
|
|
1414
|
+
# Executes a given command and processes its input, output, and error streams.
|
1415
|
+
#
|
1416
|
+
# @param [Array<String>] command the command to execute along with its arguments.
|
1417
|
+
# @yield [stdin, stdout, stderr, thread] if a block is provided, it yields input, output, error lines, and the execution thread.
|
1418
|
+
# @return [Integer] the exit status of the executed command (0 to 255).
|
1419
|
+
#
|
1420
|
+
# @example
|
1421
|
+
# status = execute_command_with_streams(['ls', '-la']) do |stdin, stdout, stderr, thread|
|
1422
|
+
# puts "STDOUT: #{stdout}" if stdout
|
1423
|
+
# puts "STDERR: #{stderr}" if stderr
|
1424
|
+
# end
|
1425
|
+
# puts "Command exited with status: #{status}"
|
1426
|
+
def execute_command_with_streams(command)
|
1427
|
+
exit_status = nil
|
1428
|
+
|
1429
|
+
Open3.popen3(*command) do |stdin, stdout, stderr, exec_thread|
|
1430
|
+
# Handle stdout stream
|
1431
|
+
handle_stream(stream: stdout, file_type: ExecutionStreams::STD_OUT) do |line|
|
1432
|
+
yield nil, line, nil, exec_thread if block_given?
|
1433
|
+
end
|
1434
|
+
|
1435
|
+
# Handle stderr stream
|
1436
|
+
handle_stream(stream: stderr, file_type: ExecutionStreams::STD_ERR) do |line|
|
1437
|
+
yield nil, nil, line, exec_thread if block_given?
|
1438
|
+
end
|
1439
|
+
|
1440
|
+
# Handle stdin stream
|
1441
|
+
input_thread = handle_stream(stream: $stdin, file_type: ExecutionStreams::STD_IN) do |line|
|
1442
|
+
stdin.puts(line)
|
1443
|
+
yield line, nil, nil, exec_thread if block_given?
|
1444
|
+
end
|
1445
|
+
|
1446
|
+
# Wait for all streams to be processed
|
1447
|
+
wait_for_stream_processing
|
1448
|
+
exec_thread.join
|
1449
|
+
|
1450
|
+
# Ensure the input thread is killed if it's still alive
|
1451
|
+
sleep 0.1
|
1452
|
+
input_thread.kill if input_thread&.alive?
|
1453
|
+
|
1454
|
+
# Retrieve the exit status
|
1455
|
+
exit_status = exec_thread.value.exitstatus
|
1456
|
+
end
|
1457
|
+
|
1458
|
+
exit_status
|
1459
|
+
end
|
1460
|
+
|
1247
1461
|
# Executes a block of code that has been approved for execution.
|
1248
1462
|
# It sets the script block name, writes command files if required, and handles the execution
|
1249
1463
|
# including output formatting and summarization.
|
@@ -1294,7 +1508,7 @@ module MarkdownExec
|
|
1294
1508
|
### options_state.load_file_link_state
|
1295
1509
|
link_state = LinkState.new
|
1296
1510
|
link_history_push_and_next(
|
1297
|
-
curr_block_name: selected
|
1511
|
+
curr_block_name: selected.pub_name,
|
1298
1512
|
curr_document_filename: @delegate_object[:filename],
|
1299
1513
|
inherited_block_names: ((link_state&.inherited_block_names || []) + block_names).sort.uniq,
|
1300
1514
|
inherited_dependencies: (link_state&.inherited_dependencies || {}).merge(dependencies || {}), ### merge, not replace, key data
|
@@ -1310,7 +1524,7 @@ module MarkdownExec
|
|
1310
1524
|
code_lines = set_environment_variables_for_block(selected)
|
1311
1525
|
dependencies = {}
|
1312
1526
|
link_history_push_and_next(
|
1313
|
-
curr_block_name: selected
|
1527
|
+
curr_block_name: selected.pub_name,
|
1314
1528
|
curr_document_filename: @delegate_object[:filename],
|
1315
1529
|
inherited_block_names: ((link_state&.inherited_block_names || []) + block_names).sort.uniq,
|
1316
1530
|
inherited_dependencies: (link_state&.inherited_dependencies || {}).merge(dependencies || {}), ### merge, not replace, key data
|
@@ -1437,7 +1651,7 @@ module MarkdownExec
|
|
1437
1651
|
return
|
1438
1652
|
end
|
1439
1653
|
|
1440
|
-
@delegate_object[:block_name] = block_state.block
|
1654
|
+
@delegate_object[:block_name] = block_state.block.pub_name
|
1441
1655
|
@menu_user_clicked_back_link = block_state.state == MenuState::BACK
|
1442
1656
|
end
|
1443
1657
|
|
@@ -1446,7 +1660,7 @@ module MarkdownExec
|
|
1446
1660
|
Thread.new do
|
1447
1661
|
stream.each_line do |line|
|
1448
1662
|
line.strip!
|
1449
|
-
@run_state.files[file_type] << line
|
1663
|
+
@run_state.files[file_type] << line if @run_state.files
|
1450
1664
|
|
1451
1665
|
if @delegate_object[:output_stdout]
|
1452
1666
|
# print line
|
@@ -1546,23 +1760,7 @@ module MarkdownExec
|
|
1546
1760
|
|
1547
1761
|
if link_block_data.fetch(LinkKeys::EXEC, false)
|
1548
1762
|
@run_state.files = Hash.new([])
|
1549
|
-
|
1550
|
-
Open3.popen3(cmd) do |stdin, stdout, stderr, _exec_thr|
|
1551
|
-
handle_stream(stream: stdout, file_type: ExecutionStreams::STD_OUT) do |line|
|
1552
|
-
output_lines.push(line)
|
1553
|
-
end
|
1554
|
-
handle_stream(stream: stderr, file_type: ExecutionStreams::STD_ERR) do |line|
|
1555
|
-
output_lines.push(line)
|
1556
|
-
end
|
1557
|
-
|
1558
|
-
in_thr = handle_stream(stream: $stdin, file_type: ExecutionStreams::STD_IN) do |line|
|
1559
|
-
stdin.puts(line)
|
1560
|
-
end
|
1561
|
-
|
1562
|
-
wait_for_stream_processing
|
1563
|
-
sleep 0.1
|
1564
|
-
in_thr.kill if in_thr&.alive?
|
1565
|
-
end
|
1763
|
+
execute_command_with_streams([cmd])
|
1566
1764
|
|
1567
1765
|
## select output_lines that look like assignment or match other specs
|
1568
1766
|
#
|
@@ -1585,13 +1783,13 @@ module MarkdownExec
|
|
1585
1783
|
label_format_below = @delegate_object[:shell_code_label_format_below]
|
1586
1784
|
|
1587
1785
|
[label_format_above && format(label_format_above,
|
1588
|
-
block_source.merge({ block_name: selected
|
1786
|
+
block_source.merge({ block_name: selected.pub_name }))] +
|
1589
1787
|
output_lines.map do |line|
|
1590
1788
|
re = Regexp.new(link_block_data.fetch('pattern', '(?<line>.*)'))
|
1591
1789
|
re.gsub_format(line, link_block_data.fetch('format', '%{line}')) if re =~ line
|
1592
1790
|
end.compact +
|
1593
1791
|
[label_format_below && format(label_format_below,
|
1594
|
-
block_source.merge({ block_name: selected
|
1792
|
+
block_source.merge({ block_name: selected.pub_name }))]
|
1595
1793
|
end
|
1596
1794
|
|
1597
1795
|
def link_history_push_and_next(
|
@@ -1655,7 +1853,7 @@ module MarkdownExec
|
|
1655
1853
|
def load_cli_or_user_selected_block(all_blocks: [], menu_blocks: [], default: nil)
|
1656
1854
|
if @delegate_object[:block_name].present?
|
1657
1855
|
block = all_blocks.find do |item|
|
1658
|
-
item
|
1856
|
+
item.pub_name == @delegate_object[:block_name]
|
1659
1857
|
end&.merge(block_name_from_ui: false)
|
1660
1858
|
else
|
1661
1859
|
block_state = wait_for_user_selected_block(all_blocks, menu_blocks,
|
@@ -1682,6 +1880,44 @@ module MarkdownExec
|
|
1682
1880
|
expanded_expression
|
1683
1881
|
end
|
1684
1882
|
end
|
1883
|
+
|
1884
|
+
# private
|
1885
|
+
|
1886
|
+
# def read_block_name(line)
|
1887
|
+
# bm = extract_named_captures_from_option(line, @delegate_object[:block_name_match])
|
1888
|
+
# name = bm[:title]
|
1889
|
+
|
1890
|
+
# if @delegate_object[:block_name_nick_match].present? && line =~ Regexp.new(@delegate_object[:block_name_nick_match])
|
1891
|
+
# name = $~[0]
|
1892
|
+
# else
|
1893
|
+
# name = bm && bm[1] ? bm[:title] : name
|
1894
|
+
# end
|
1895
|
+
# name
|
1896
|
+
# end
|
1897
|
+
|
1898
|
+
# # Loads auto link block.
|
1899
|
+
# def load_auto_link_block(all_blocks, link_state, mdoc, block_source:)
|
1900
|
+
# block_name = @delegate_object[:document_load_link_block_name]
|
1901
|
+
# return unless block_name.present? && @most_recent_loaded_filename != @delegate_object[:filename]
|
1902
|
+
|
1903
|
+
# block = HashDelegator.block_find(all_blocks, :oname, block_name)
|
1904
|
+
# return unless block
|
1905
|
+
|
1906
|
+
# if block.fetch(:shell, '') != BlockType::LINK
|
1907
|
+
# HashDelegator.error_handler('must be Link block type', { abort: true })
|
1908
|
+
|
1909
|
+
# else
|
1910
|
+
# # debounce_reset
|
1911
|
+
# push_link_history_and_trigger_load(
|
1912
|
+
# link_block_body: block.fetch(:body, ''),
|
1913
|
+
# mdoc: mdoc,
|
1914
|
+
# selected: block,
|
1915
|
+
# link_state: link_state,
|
1916
|
+
# block_source: block_source
|
1917
|
+
# )
|
1918
|
+
# end
|
1919
|
+
# end
|
1920
|
+
|
1685
1921
|
# Handle expression with wildcard characters
|
1686
1922
|
def load_filespec_wildcard_expansion(expr, auto_load_single: false)
|
1687
1923
|
files = find_files(expr)
|
@@ -1717,6 +1953,7 @@ module MarkdownExec
|
|
1717
1953
|
# recreate menu with new options
|
1718
1954
|
#
|
1719
1955
|
all_blocks, mdoc = mdoc_and_blocks_from_nested_files if load_auto_opts_block(all_blocks)
|
1956
|
+
# load_auto_link_block(all_blocks, link_state, mdoc, block_source: {})
|
1720
1957
|
|
1721
1958
|
menu_blocks = mdoc.fcbs_per_options(@delegate_object)
|
1722
1959
|
add_menu_chrome_blocks!(menu_blocks: menu_blocks, link_state: link_state)
|
@@ -1793,7 +2030,7 @@ module MarkdownExec
|
|
1793
2030
|
#
|
1794
2031
|
return unless item
|
1795
2032
|
|
1796
|
-
item[:dname] = "#{name} (#{count} #{type})"
|
2033
|
+
item[:dname] = type.present? ? "#{name} (#{count} #{type})" : name
|
1797
2034
|
if count.positive?
|
1798
2035
|
item.delete(:disabled)
|
1799
2036
|
else
|
@@ -1902,7 +2139,7 @@ module MarkdownExec
|
|
1902
2139
|
else
|
1903
2140
|
# no history exists; must have been called independently => retain script
|
1904
2141
|
link_history_push_and_next(
|
1905
|
-
curr_block_name: selected
|
2142
|
+
curr_block_name: selected.pub_name,
|
1906
2143
|
curr_document_filename: @delegate_object[:filename],
|
1907
2144
|
inherited_block_names: ((link_state&.inherited_block_names || []) + block_names).sort.uniq,
|
1908
2145
|
inherited_dependencies: (link_state&.inherited_dependencies || {}).merge(dependencies || {}), ### merge, not replace, key data
|
@@ -2036,6 +2273,14 @@ module MarkdownExec
|
|
2036
2273
|
exit 1
|
2037
2274
|
end
|
2038
2275
|
|
2276
|
+
def prompt_for_command(prompt)
|
2277
|
+
print prompt
|
2278
|
+
|
2279
|
+
gets.chomp
|
2280
|
+
rescue Interrupt
|
2281
|
+
nil
|
2282
|
+
end
|
2283
|
+
|
2039
2284
|
# Prompts the user to enter a path or name to substitute into the wildcard expression.
|
2040
2285
|
# If interrupted by the user (e.g., pressing Ctrl-C), it returns nil.
|
2041
2286
|
#
|
@@ -2132,8 +2377,7 @@ module MarkdownExec
|
|
2132
2377
|
# user prompt to exit if the menu will be displayed again
|
2133
2378
|
#
|
2134
2379
|
def prompt_user_exit(block_name_from_cli:, selected:)
|
2135
|
-
|
2136
|
-
selected[:shell] == BlockType::BASH &&
|
2380
|
+
selected[:shell] == BlockType::BASH &&
|
2137
2381
|
@delegate_object[:pause_after_script_execution] &&
|
2138
2382
|
prompt_select_continue == MenuState::EXIT
|
2139
2383
|
end
|
@@ -2154,7 +2398,7 @@ module MarkdownExec
|
|
2154
2398
|
#
|
2155
2399
|
if mdoc
|
2156
2400
|
code_info = mdoc.collect_recursively_required_code(
|
2157
|
-
anyname: selected
|
2401
|
+
anyname: selected.pub_name,
|
2158
2402
|
label_format_above: @delegate_object[:shell_code_label_format_above],
|
2159
2403
|
label_format_below: @delegate_object[:shell_code_label_format_below],
|
2160
2404
|
block_source: block_source
|
@@ -2171,7 +2415,7 @@ module MarkdownExec
|
|
2171
2415
|
# load key and values from link block into current environment
|
2172
2416
|
#
|
2173
2417
|
if link_block_data[LinkKeys::VARS]
|
2174
|
-
code_lines.push BashCommentFormatter.format_comment(selected
|
2418
|
+
code_lines.push BashCommentFormatter.format_comment(selected.pub_name)
|
2175
2419
|
(link_block_data[LinkKeys::VARS] || []).each do |(key, value)|
|
2176
2420
|
ENV[key] = value.to_s
|
2177
2421
|
code_lines.push(assign_key_value_in_bash(key, value))
|
@@ -2200,7 +2444,7 @@ module MarkdownExec
|
|
2200
2444
|
|
2201
2445
|
else
|
2202
2446
|
link_history_push_and_next(
|
2203
|
-
curr_block_name: selected
|
2447
|
+
curr_block_name: selected.pub_name,
|
2204
2448
|
curr_document_filename: @delegate_object[:filename],
|
2205
2449
|
inherited_block_names: ((link_state&.inherited_block_names || []) + block_names).sort.uniq,
|
2206
2450
|
inherited_dependencies: (link_state&.inherited_dependencies || {}).merge(dependencies || {}), ### merge, not replace, key data
|
@@ -2243,6 +2487,39 @@ module MarkdownExec
|
|
2243
2487
|
))
|
2244
2488
|
end
|
2245
2489
|
|
2490
|
+
# Registers console attributes by modifying the options hash.
|
2491
|
+
# This method handles terminal resizing and adjusts the console dimensions
|
2492
|
+
# and pagination settings based on the current terminal size.
|
2493
|
+
#
|
2494
|
+
# @param opts [Hash] a hash containing various options for the console settings.
|
2495
|
+
# - :console_width [Integer, nil] The width of the console. If not provided or if the terminal is resized, it will be set to the current console width.
|
2496
|
+
# - :console_height [Integer, nil] The height of the console. If not provided or if the terminal is resized, it will be set to the current console height.
|
2497
|
+
# - :console_winsize [Array<Integer>, nil] The dimensions of the console [height, width]. If not provided or if the terminal is resized, it will be set to the current console dimensions.
|
2498
|
+
# - :select_page_height [Integer, nil] The height of the page for selection. If not provided or if not positive, it will be set to the maximum of (console height - 3) or 4.
|
2499
|
+
# - :per_page [Integer, nil] The number of items per page. If :select_page_height is not provided or if not positive, it will be set to the maximum of (console height - 3) or 4.
|
2500
|
+
#
|
2501
|
+
# @raise [StandardError] If an error occurs during the process, it will be caught and handled by calling HashDelegator.error_handler with 'register_console_attributes' and { abort: true }.
|
2502
|
+
#
|
2503
|
+
# @example
|
2504
|
+
# opts = { console_width: nil, console_height: nil, select_page_height: nil }
|
2505
|
+
# register_console_attributes(opts)
|
2506
|
+
# # opts will be updated with the current console dimensions and pagination settings.
|
2507
|
+
def register_console_attributes(opts)
|
2508
|
+
begin
|
2509
|
+
if (resized = @delegate_object[:menu_resize_terminal])
|
2510
|
+
resize_terminal
|
2511
|
+
end
|
2512
|
+
|
2513
|
+
if resized || !opts[:console_width]
|
2514
|
+
opts[:console_height], opts[:console_width] = opts[:console_winsize] = IO.console.winsize
|
2515
|
+
end
|
2516
|
+
|
2517
|
+
opts[:per_page] = opts[:select_page_height] = [opts[:console_height] - 3, 4].max unless opts[:select_page_height]&.positive?
|
2518
|
+
rescue StandardError
|
2519
|
+
HashDelegator.error_handler('register_console_attributes', { abort: true })
|
2520
|
+
end
|
2521
|
+
end
|
2522
|
+
|
2246
2523
|
# Check if the delegate object responds to a given method.
|
2247
2524
|
# @param method_name [Symbol] The name of the method to check.
|
2248
2525
|
# @param include_private [Boolean] Whether to include private methods in the check.
|
@@ -2323,10 +2600,7 @@ module MarkdownExec
|
|
2323
2600
|
def select_option_with_metadata(prompt_text, names, opts = {})
|
2324
2601
|
## configure to environment
|
2325
2602
|
#
|
2326
|
-
|
2327
|
-
require 'io/console'
|
2328
|
-
opts[:per_page] = opts[:select_page_height] = [IO.console.winsize[0] - 3, 4].max
|
2329
|
-
end
|
2603
|
+
register_console_attributes(opts)
|
2330
2604
|
|
2331
2605
|
# crashes if all menu options are disabled
|
2332
2606
|
selection = @prompt.select(prompt_text,
|
@@ -2528,6 +2802,8 @@ module MarkdownExec
|
|
2528
2802
|
@process_mutex.synchronize do
|
2529
2803
|
@process_cv.wait(@process_mutex)
|
2530
2804
|
end
|
2805
|
+
rescue Interrupt
|
2806
|
+
# user interrupts process
|
2531
2807
|
end
|
2532
2808
|
|
2533
2809
|
def wait_for_user_selected_block(all_blocks, menu_blocks, default)
|
@@ -2568,7 +2844,7 @@ module MarkdownExec
|
|
2568
2844
|
time_now = Time.now.utc
|
2569
2845
|
@run_state.saved_script_filename =
|
2570
2846
|
SavedAsset.script_name(
|
2571
|
-
blockname: selected
|
2847
|
+
blockname: selected.pub_name,
|
2572
2848
|
filename: @delegate_object[:filename],
|
2573
2849
|
prefix: @delegate_object[:saved_script_filename_prefix],
|
2574
2850
|
time: time_now
|
@@ -2661,6 +2937,21 @@ module MarkdownExec
|
|
2661
2937
|
|
2662
2938
|
def self.next_link_state(*args, **kwargs, &block)
|
2663
2939
|
super
|
2940
|
+
# result = super
|
2941
|
+
|
2942
|
+
# @logger ||= StdOutErrLogger.new
|
2943
|
+
# @logger.unknown(
|
2944
|
+
# HashDelegator.clean_hash_recursively(
|
2945
|
+
# { "HashDelegator.next_link_state":
|
2946
|
+
# { 'args': args,
|
2947
|
+
# 'at': Time.now.strftime('%FT%TZ'),
|
2948
|
+
# 'for': /[^\/]+:\d+/.match(caller.first)[0],
|
2949
|
+
# 'kwargs': kwargs,
|
2950
|
+
# 'return': result } }
|
2951
|
+
# )
|
2952
|
+
# )
|
2953
|
+
|
2954
|
+
# result
|
2664
2955
|
end
|
2665
2956
|
end
|
2666
2957
|
end
|
@@ -2912,7 +3203,6 @@ module MarkdownExec
|
|
2912
3203
|
|
2913
3204
|
def test_blocks_from_nested_files
|
2914
3205
|
result = @hd.blocks_from_nested_files
|
2915
|
-
|
2916
3206
|
assert_kind_of Array, result
|
2917
3207
|
assert_kind_of FCB, result.first
|
2918
3208
|
end
|