markdown_exec 1.7 → 1.8.1
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 -2
- data/CHANGELOG.md +28 -0
- data/Gemfile.lock +1 -1
- data/Rakefile +1 -0
- data/bin/tab_completion.sh +19 -3
- data/examples/colors.md +48 -0
- data/examples/include.md +11 -4
- data/examples/opts.md +7 -0
- data/lib/ansi_formatter.rb +161 -0
- data/lib/directory_searcher.rb +239 -0
- data/lib/env.rb +1 -2
- data/lib/hash_delegator.rb +152 -58
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/markdown_exec.rb +66 -29
- data/lib/mdoc.rb +148 -33
- data/lib/menu.src.yml +94 -17
- data/lib/menu.yml +86 -18
- data/lib/option_value.rb +2 -4
- data/lib/regexp.rb +1 -2
- data/lib/saved_assets.rb +2 -4
- data/lib/saved_files_matcher.rb +3 -7
- data/lib/tap.rb +2 -5
- metadata +5 -2
data/lib/hash_delegator.rb
CHANGED
@@ -18,6 +18,7 @@ require_relative 'block_label'
|
|
18
18
|
require_relative 'block_types'
|
19
19
|
require_relative 'cached_nested_file_reader'
|
20
20
|
require_relative 'constants'
|
21
|
+
require_relative 'directory_searcher'
|
21
22
|
require_relative 'exceptions'
|
22
23
|
require_relative 'fcb'
|
23
24
|
require_relative 'filter'
|
@@ -34,6 +35,56 @@ class String
|
|
34
35
|
end
|
35
36
|
end
|
36
37
|
|
38
|
+
# This module provides methods for compacting and converting data structures.
|
39
|
+
module CompactionHelpers
|
40
|
+
# Converts an array of key-value pairs into a hash, applying compaction to the values.
|
41
|
+
# Each value is processed by `compact_hash` to remove ineligible elements.
|
42
|
+
#
|
43
|
+
# @param array [Array] The array of key-value pairs to be converted.
|
44
|
+
# @return [Hash] A hash with keys from the array and compacted values.
|
45
|
+
def compact_and_convert_array_to_hash(array)
|
46
|
+
array.transform_values do |value|
|
47
|
+
compact_hash(value)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Compacts a hash by removing ineligible elements.
|
52
|
+
# It filters out nil, empty arrays, empty hashes, and empty strings from its values.
|
53
|
+
# It also removes entries with :random as the key.
|
54
|
+
#
|
55
|
+
# @param hash [Hash] The hash to be compacted.
|
56
|
+
# @return [Hash] A compacted version of the input hash.
|
57
|
+
def compact_hash(hash)
|
58
|
+
hash.map do |key, value|
|
59
|
+
next if value_ineligible?(value) || key == :random
|
60
|
+
|
61
|
+
[key, value]
|
62
|
+
end.compact.to_h
|
63
|
+
end
|
64
|
+
|
65
|
+
# Converts a hash into another hash with indexed keys, applying compaction to the values.
|
66
|
+
# The keys are indexed, and the values are compacted using `compact_and_convert_array_to_hash`.
|
67
|
+
#
|
68
|
+
# @param hash [Hash] The hash to be converted and compacted.
|
69
|
+
# @return [Hash] A hash with indexed keys and the compacted original values.
|
70
|
+
def compact_and_index_hash(hash)
|
71
|
+
compact_and_convert_array_to_hash(hash.map.with_index do |value, index|
|
72
|
+
[index, value]
|
73
|
+
end.to_h)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
# Determines if a value is ineligible for inclusion in a compacted hash.
|
79
|
+
# Ineligible values are nil, empty arrays, empty hashes, and empty strings.
|
80
|
+
#
|
81
|
+
# @param value [Object] The value to be checked.
|
82
|
+
# @return [Boolean] True if the value is ineligible, false otherwise.
|
83
|
+
def value_ineligible?(value)
|
84
|
+
[nil, [], {}, ''].include?(value)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
37
88
|
module MarkdownExec
|
38
89
|
class DebugHelper
|
39
90
|
# Class-level variable to store history of printed messages
|
@@ -51,12 +102,16 @@ module MarkdownExec
|
|
51
102
|
end
|
52
103
|
|
53
104
|
class HashDelegator
|
54
|
-
attr_accessor :run_state
|
105
|
+
attr_accessor :most_recent_loaded_filename, :pass_args, :run_state
|
106
|
+
|
107
|
+
include CompactionHelpers
|
55
108
|
|
56
109
|
def initialize(delegate_object = {})
|
57
110
|
@delegate_object = delegate_object
|
58
111
|
@prompt = tty_prompt_without_disabled_symbol
|
59
112
|
|
113
|
+
@most_recent_loaded_filename = nil
|
114
|
+
@pass_args = []
|
60
115
|
@run_state = OpenStruct.new(
|
61
116
|
link_history: []
|
62
117
|
)
|
@@ -267,6 +322,25 @@ module MarkdownExec
|
|
267
322
|
true
|
268
323
|
end
|
269
324
|
|
325
|
+
def runtime_exception(exception_sym, name, items)
|
326
|
+
if @delegate_object[exception_sym] != 0
|
327
|
+
data = { name: name, detail: items.join(', ') }
|
328
|
+
warn(
|
329
|
+
format(
|
330
|
+
@delegate_object.fetch(:exception_format_name, "\n%{name}"),
|
331
|
+
data
|
332
|
+
).send(@delegate_object.fetch(:exception_color_name, :red)) +
|
333
|
+
format(
|
334
|
+
@delegate_object.fetch(:exception_format_detail, " - %{detail}\n"),
|
335
|
+
data
|
336
|
+
).send(@delegate_object.fetch(:exception_color_detail, :yellow))
|
337
|
+
)
|
338
|
+
end
|
339
|
+
return unless (@delegate_object[exception_sym]).positive?
|
340
|
+
|
341
|
+
exit @delegate_object[exception_sym]
|
342
|
+
end
|
343
|
+
|
270
344
|
# Collects required code lines based on the selected block and the delegate object's configuration.
|
271
345
|
# If the block type is VARS, it also sets environment variables based on the block's content.
|
272
346
|
#
|
@@ -274,13 +348,22 @@ module MarkdownExec
|
|
274
348
|
# @param selected [Hash] The selected block.
|
275
349
|
# @return [Array<String>] Required code blocks as an array of lines.
|
276
350
|
def collect_required_code_lines(mdoc, selected)
|
277
|
-
if selected[:shell] == BlockType::VARS
|
278
|
-
set_environment_variables(selected)
|
279
|
-
end
|
351
|
+
set_environment_variables(selected) if selected[:shell] == BlockType::VARS
|
280
352
|
|
281
353
|
required = mdoc.collect_recursively_required_code(
|
282
|
-
@delegate_object[:block_name],
|
354
|
+
@delegate_object[:block_name],
|
355
|
+
label_format_above: @delegate_object[:shell_code_label_format_above],
|
356
|
+
label_format_below: @delegate_object[:shell_code_label_format_below]
|
283
357
|
)
|
358
|
+
if required[:unmet_dependencies].present?
|
359
|
+
warn format_and_highlight_dependencies(required[:dependencies],
|
360
|
+
highlight: required[:unmet_dependencies])
|
361
|
+
runtime_exception(:runtime_exception_error_level,
|
362
|
+
'unmet_dependencies, flag: runtime_exception_error_level', required[:unmet_dependencies])
|
363
|
+
elsif true
|
364
|
+
warn format_and_highlight_dependencies(required[:dependencies],
|
365
|
+
highlight: [@delegate_object[:block_name]])
|
366
|
+
end
|
284
367
|
read_required_blocks_from_temp_file + required[:code]
|
285
368
|
end
|
286
369
|
|
@@ -301,6 +384,7 @@ module MarkdownExec
|
|
301
384
|
@run_state.files = Hash.new([])
|
302
385
|
@run_state.options = @delegate_object
|
303
386
|
@run_state.started_at = Time.now.utc
|
387
|
+
# rbp
|
304
388
|
|
305
389
|
Open3.popen3(@delegate_object[:shell],
|
306
390
|
'-c', command,
|
@@ -489,11 +573,11 @@ module MarkdownExec
|
|
489
573
|
fcb.derive_title_from_body
|
490
574
|
end
|
491
575
|
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
!current_item&.fetch(:oname).present?
|
576
|
+
# delete the current line if it is empty and the previous is also empty
|
577
|
+
def delete_consecutive_blank_lines!(blocks_menu)
|
578
|
+
blocks_menu.process_and_conditionally_delete! do |prev_item, current_item, _next_item|
|
579
|
+
prev_item&.fetch(:chrome, nil) && !prev_item&.fetch(:oname).present? &&
|
580
|
+
current_item&.fetch(:chrome, nil) && !current_item&.fetch(:oname).present?
|
497
581
|
end
|
498
582
|
end
|
499
583
|
|
@@ -575,8 +659,8 @@ module MarkdownExec
|
|
575
659
|
#
|
576
660
|
# @param required_lines [Array<String>] The lines of code to be executed.
|
577
661
|
# @param selected [FCB] The selected functional code block object.
|
578
|
-
def execute_approved_block(required_lines = [],
|
579
|
-
set_script_block_name(selected)
|
662
|
+
def execute_approved_block(required_lines = [], _selected = FCB.new)
|
663
|
+
# set_script_block_name(selected)
|
580
664
|
write_command_file_if_needed(required_lines)
|
581
665
|
format_and_execute_command(required_lines)
|
582
666
|
post_execution_process
|
@@ -596,8 +680,7 @@ module MarkdownExec
|
|
596
680
|
formatted_command = lines.flatten.join("\n")
|
597
681
|
@fout.fout fetch_color(data_sym: :script_execution_head,
|
598
682
|
color_sym: :script_execution_frame_color)
|
599
|
-
command_execute(formatted_command,
|
600
|
-
args: @delegate_object.fetch(:s_pass_args, []))
|
683
|
+
command_execute(formatted_command, args: @pass_args)
|
601
684
|
@fout.fout fetch_color(data_sym: :script_execution_tail,
|
602
685
|
color_sym: :script_execution_frame_color)
|
603
686
|
end
|
@@ -708,7 +791,8 @@ module MarkdownExec
|
|
708
791
|
return
|
709
792
|
end
|
710
793
|
|
711
|
-
@delegate_object[:block_name] = block_state.block[:
|
794
|
+
@delegate_object[:block_name] = block_state.block[:oname]
|
795
|
+
# rbp
|
712
796
|
@menu_user_clicked_back_link = block_state.state == MenuState::BACK
|
713
797
|
end
|
714
798
|
|
@@ -720,14 +804,11 @@ module MarkdownExec
|
|
720
804
|
# @param selected [Hash] The selected item from the menu to be executed.
|
721
805
|
# @return [LoadFileNextBlock] An object indicating whether to load the next block or reuse the current one.
|
722
806
|
def handle_generic_block(mdoc, selected)
|
807
|
+
# rbp
|
723
808
|
required_lines = collect_required_code_lines(mdoc, selected)
|
724
809
|
output_or_approval = @delegate_object[:output_script] || @delegate_object[:user_must_approve]
|
725
|
-
|
726
810
|
display_required_code(required_lines) if output_or_approval
|
727
|
-
|
728
811
|
allow_execution = @delegate_object[:user_must_approve] ? prompt_for_user_approval(required_lines) : true
|
729
|
-
|
730
|
-
@delegate_object[:s_ir_approve] = allow_execution
|
731
812
|
execute_approved_block(required_lines, selected) if allow_execution
|
732
813
|
|
733
814
|
LoadFileNextBlock.new(LoadFile::Reuse, '')
|
@@ -769,7 +850,7 @@ module MarkdownExec
|
|
769
850
|
# @return [LoadFileNextBlock] An instance indicating the next action for loading files.
|
770
851
|
def handle_opts_block(selected, tgt2 = nil)
|
771
852
|
data = YAML.load(selected[:body].join("\n"))
|
772
|
-
data.each do |key, value|
|
853
|
+
(data || []).each do |key, value|
|
773
854
|
update_delegate_and_target(key, value, tgt2)
|
774
855
|
if @delegate_object[:menu_opts_set_format].present?
|
775
856
|
print_formatted_option(key,
|
@@ -931,7 +1012,7 @@ module MarkdownExec
|
|
931
1012
|
# @return [Boolean, nil] True if values were modified, nil otherwise.
|
932
1013
|
def load_auto_blocks(all_blocks)
|
933
1014
|
block_name = @delegate_object[:document_load_opts_block_name]
|
934
|
-
unless block_name.present? && @
|
1015
|
+
unless block_name.present? && @most_recent_loaded_filename != @delegate_object[:filename]
|
935
1016
|
return
|
936
1017
|
end
|
937
1018
|
|
@@ -939,7 +1020,7 @@ module MarkdownExec
|
|
939
1020
|
return unless block
|
940
1021
|
|
941
1022
|
handle_opts_block(block, @delegate_object)
|
942
|
-
@
|
1023
|
+
@most_recent_loaded_filename = @delegate_object[:filename]
|
943
1024
|
true
|
944
1025
|
end
|
945
1026
|
|
@@ -999,7 +1080,7 @@ module MarkdownExec
|
|
999
1080
|
|
1000
1081
|
menu_blocks = mdoc.fcbs_per_options(@delegate_object)
|
1001
1082
|
add_menu_chrome_blocks!(menu_blocks)
|
1002
|
-
|
1083
|
+
delete_consecutive_blank_lines!(menu_blocks) if true ### compress empty lines
|
1003
1084
|
[all_blocks, menu_blocks, mdoc]
|
1004
1085
|
end
|
1005
1086
|
|
@@ -1028,9 +1109,10 @@ module MarkdownExec
|
|
1028
1109
|
end
|
1029
1110
|
|
1030
1111
|
def next_block_name_from_command_line_arguments
|
1031
|
-
return MenuControl::Repeat unless @delegate_object[:
|
1112
|
+
return MenuControl::Repeat unless @delegate_object[:input_cli_rest].present?
|
1032
1113
|
|
1033
|
-
|
1114
|
+
# rbp
|
1115
|
+
@delegate_object[:block_name] = @delegate_object[:input_cli_rest].pop
|
1034
1116
|
MenuControl::Fresh
|
1035
1117
|
end
|
1036
1118
|
|
@@ -1209,7 +1291,7 @@ module MarkdownExec
|
|
1209
1291
|
# Markdown document, obtain approval, and execute the chosen block of code.
|
1210
1292
|
#
|
1211
1293
|
# @return [Nil] Returns nil if no code block is selected or an error occurs.
|
1212
|
-
def select_approve_and_execute_block
|
1294
|
+
def select_approve_and_execute_block(_execute: true)
|
1213
1295
|
@menu_base_options = @delegate_object
|
1214
1296
|
repeat_menu = @menu_base_options[:block_name].present? ? MenuControl::Fresh : MenuControl::Repeat
|
1215
1297
|
load_file_next_block = LoadFileNextBlock.new(LoadFile::Reuse)
|
@@ -1222,12 +1304,25 @@ module MarkdownExec
|
|
1222
1304
|
loop do
|
1223
1305
|
@delegate_object = @menu_base_options.dup
|
1224
1306
|
@menu_base_options[:filename] = @menu_state_filename
|
1307
|
+
# rbp
|
1225
1308
|
@menu_base_options[:block_name] = @menu_state_block_name
|
1226
1309
|
@menu_state_filename = nil
|
1227
1310
|
@menu_state_block_name = nil
|
1228
|
-
|
1229
1311
|
@menu_user_clicked_back_link = false
|
1312
|
+
|
1230
1313
|
blocks_in_file, menu_blocks, mdoc = mdoc_menu_and_blocks_from_nested_files
|
1314
|
+
if @delegate_object[:dump_blocks_in_file]
|
1315
|
+
warn format_and_highlight_dependencies(
|
1316
|
+
compact_and_index_hash(blocks_in_file),
|
1317
|
+
label: 'blocks_in_file'
|
1318
|
+
)
|
1319
|
+
end
|
1320
|
+
if @delegate_object[:dump_menu_blocks]
|
1321
|
+
warn format_and_highlight_dependencies(
|
1322
|
+
compact_and_index_hash(menu_blocks),
|
1323
|
+
label: 'menu_blocks'
|
1324
|
+
)
|
1325
|
+
end
|
1231
1326
|
block_state = command_or_user_selected_block(blocks_in_file,
|
1232
1327
|
menu_blocks, default)
|
1233
1328
|
return if block_state.state == MenuState::EXIT
|
@@ -1238,9 +1333,15 @@ module MarkdownExec
|
|
1238
1333
|
# error_handler("Block not found -- #{opts[:block_name]}", { abort: true })
|
1239
1334
|
end
|
1240
1335
|
|
1336
|
+
if @delegate_object[:dump_selected_block]
|
1337
|
+
warn block_state.block.to_yaml.sub(/^(?:---\n)?/, "Block:\n")
|
1338
|
+
end
|
1339
|
+
|
1340
|
+
# rbp
|
1241
1341
|
load_file_next_block = approve_and_execute_block(block_state.block,
|
1242
1342
|
mdoc)
|
1243
1343
|
default = load_file_next_block.load_file == LoadFile::Load ? nil : @delegate_object[:block_name]
|
1344
|
+
# rbp
|
1244
1345
|
@menu_base_options[:block_name] =
|
1245
1346
|
@delegate_object[:block_name] = load_file_next_block.next_block
|
1246
1347
|
@menu_base_options[:filename] = @delegate_object[:filename]
|
@@ -1469,11 +1570,15 @@ module MarkdownExec
|
|
1469
1570
|
)
|
1470
1571
|
|
1471
1572
|
block_menu = prepare_blocks_menu(menu_blocks)
|
1472
|
-
if block_menu.empty?
|
1473
|
-
|
1474
|
-
|
1573
|
+
return SelectedBlockMenuState.new(nil, MenuState::EXIT) if block_menu.empty?
|
1574
|
+
|
1575
|
+
# default value may not match if color is different from originating menu (opts changed while processing)
|
1576
|
+
selection_opts = if default && menu_blocks.map(&:dname).include?(default)
|
1577
|
+
@delegate_object.merge(default: default)
|
1578
|
+
else
|
1579
|
+
@delegate_object
|
1580
|
+
end
|
1475
1581
|
|
1476
|
-
selection_opts = default ? @delegate_object.merge(default: default) : @delegate_object
|
1477
1582
|
selection_opts.merge!(per_page: @delegate_object[:select_page_height])
|
1478
1583
|
|
1479
1584
|
selected_option = select_option_with_metadata(prompt_title, block_menu,
|
@@ -1485,13 +1590,13 @@ module MarkdownExec
|
|
1485
1590
|
def write_command_file(required_lines)
|
1486
1591
|
return unless @delegate_object[:save_executed_script]
|
1487
1592
|
|
1593
|
+
# rbp
|
1488
1594
|
time_now = Time.now.utc
|
1489
1595
|
@run_state.saved_script_filename =
|
1490
1596
|
SavedAsset.script_name(blockname: @delegate_object[:block_name],
|
1491
1597
|
filename: @delegate_object[:filename],
|
1492
1598
|
prefix: @delegate_object[:saved_script_filename_prefix],
|
1493
1599
|
time: time_now)
|
1494
|
-
|
1495
1600
|
@run_state.saved_filespec =
|
1496
1601
|
File.join(@delegate_object[:saved_script_folder],
|
1497
1602
|
@run_state.saved_script_filename)
|
@@ -1540,7 +1645,8 @@ module MarkdownExec
|
|
1540
1645
|
c1 = if mdoc
|
1541
1646
|
mdoc.collect_recursively_required_code(
|
1542
1647
|
block_name,
|
1543
|
-
|
1648
|
+
label_format_above: @delegate_object[:shell_code_label_format_above],
|
1649
|
+
label_format_below: @delegate_object[:shell_code_label_format_below]
|
1544
1650
|
)[:code]
|
1545
1651
|
else
|
1546
1652
|
[]
|
@@ -1597,16 +1703,14 @@ if $PROGRAM_NAME == __FILE__
|
|
1597
1703
|
obj = {
|
1598
1704
|
output_execution_label_format: '',
|
1599
1705
|
output_execution_label_name_color: 'plain',
|
1600
|
-
output_execution_label_value_color: 'plain'
|
1601
|
-
s_pass_args: pigeon
|
1602
|
-
# shell: 'bash'
|
1706
|
+
output_execution_label_value_color: 'plain'
|
1603
1707
|
}
|
1604
1708
|
|
1605
1709
|
c = MarkdownExec::HashDelegator.new(obj)
|
1710
|
+
c.pass_args = pigeon
|
1606
1711
|
|
1607
1712
|
# Expect that method opts_command_execute is called with argument args having value pigeon
|
1608
1713
|
c.expects(:command_execute).with(
|
1609
|
-
# obj,
|
1610
1714
|
'',
|
1611
1715
|
args: pigeon
|
1612
1716
|
)
|
@@ -2178,7 +2282,7 @@ if $PROGRAM_NAME == __FILE__
|
|
2178
2282
|
|
2179
2283
|
def test_handle_block_state_with_back
|
2180
2284
|
@mock_block_state.stubs(:state).returns(MenuState::BACK)
|
2181
|
-
@mock_block_state.stubs(:block).returns({
|
2285
|
+
@mock_block_state.stubs(:block).returns({ oname: 'sample_block' })
|
2182
2286
|
|
2183
2287
|
@hd.handle_block_state(@mock_block_state)
|
2184
2288
|
|
@@ -2189,7 +2293,7 @@ if $PROGRAM_NAME == __FILE__
|
|
2189
2293
|
|
2190
2294
|
def test_handle_block_state_with_continue
|
2191
2295
|
@mock_block_state.stubs(:state).returns(MenuState::CONTINUE)
|
2192
|
-
@mock_block_state.stubs(:block).returns({
|
2296
|
+
@mock_block_state.stubs(:block).returns({ oname: 'another_block' })
|
2193
2297
|
|
2194
2298
|
@hd.handle_block_state(@mock_block_state)
|
2195
2299
|
|
@@ -2200,7 +2304,7 @@ if $PROGRAM_NAME == __FILE__
|
|
2200
2304
|
|
2201
2305
|
def test_handle_block_state_with_other
|
2202
2306
|
@mock_block_state.stubs(:state).returns(nil) # MenuState::OTHER
|
2203
|
-
@mock_block_state.stubs(:block).returns({
|
2307
|
+
@mock_block_state.stubs(:block).returns({ oname: 'other_block' })
|
2204
2308
|
|
2205
2309
|
@hd.handle_block_state(@mock_block_state)
|
2206
2310
|
|
@@ -2279,8 +2383,6 @@ if $PROGRAM_NAME == __FILE__
|
|
2279
2383
|
assert_equal 'value2',
|
2280
2384
|
@hd.instance_variable_get(:@delegate_object)[:option2]
|
2281
2385
|
end
|
2282
|
-
|
2283
|
-
# Additional test cases can be added to cover more scenarios and edge cases.
|
2284
2386
|
end
|
2285
2387
|
|
2286
2388
|
# require 'stringio'
|
@@ -2348,8 +2450,6 @@ if $PROGRAM_NAME == __FILE__
|
|
2348
2450
|
result = @hd.history_state_partition
|
2349
2451
|
assert_equal({ unit: '', rest: '' }, result)
|
2350
2452
|
end
|
2351
|
-
|
2352
|
-
# Additional test cases can be added to cover more scenarios and edge cases.
|
2353
2453
|
end
|
2354
2454
|
|
2355
2455
|
class TestHashDelegatorHistoryStatePop < Minitest::Test
|
@@ -2376,8 +2476,6 @@ if $PROGRAM_NAME == __FILE__
|
|
2376
2476
|
ENV.fetch(MDE_HISTORY_ENV_NAME, nil)
|
2377
2477
|
assert_empty @hd.instance_variable_get(:@run_state).link_history
|
2378
2478
|
end
|
2379
|
-
|
2380
|
-
# Additional test cases can be added to cover more scenarios and edge cases.
|
2381
2479
|
end
|
2382
2480
|
|
2383
2481
|
class TestHashDelegatorHistoryStatePush < Minitest::Test
|
@@ -2410,8 +2508,6 @@ if $PROGRAM_NAME == __FILE__
|
|
2410
2508
|
{ block_name: 'selected_block',
|
2411
2509
|
filename: 'data.md' }
|
2412
2510
|
end
|
2413
|
-
|
2414
|
-
# Additional test cases can be added to cover more scenarios and edge cases.
|
2415
2511
|
end
|
2416
2512
|
|
2417
2513
|
class TestHashDelegatorIterBlocksFromNestedFiles < Minitest::Test
|
@@ -2447,32 +2543,30 @@ if $PROGRAM_NAME == __FILE__
|
|
2447
2543
|
class TestHashDelegatorLoadAutoBlocks < Minitest::Test
|
2448
2544
|
def setup
|
2449
2545
|
@hd = HashDelegator.new
|
2450
|
-
@hd.
|
2451
|
-
document_load_opts_block_name: 'load_block',
|
2452
|
-
s_most_recent_filename: 'old_file',
|
2453
|
-
filename: 'new_file'
|
2454
|
-
})
|
2455
|
-
@hd.stubs(:block_find).returns({}) # Assuming it returns a block
|
2546
|
+
@hd.stubs(:block_find).returns({})
|
2456
2547
|
@hd.stubs(:handle_opts_block)
|
2457
2548
|
end
|
2458
2549
|
|
2459
2550
|
def test_load_auto_blocks_with_new_filename
|
2551
|
+
@hd.instance_variable_set(:@delegate_object, {
|
2552
|
+
document_load_opts_block_name: 'load_block',
|
2553
|
+
filename: 'new_file'
|
2554
|
+
})
|
2460
2555
|
assert @hd.load_auto_blocks([])
|
2461
2556
|
end
|
2462
2557
|
|
2463
2558
|
def test_load_auto_blocks_with_same_filename
|
2464
2559
|
@hd.instance_variable_set(:@delegate_object, {
|
2465
2560
|
document_load_opts_block_name: 'load_block',
|
2466
|
-
s_most_recent_filename: 'new_file',
|
2467
2561
|
filename: 'new_file'
|
2468
2562
|
})
|
2563
|
+
@hd.instance_variable_set(:@most_recent_loaded_filename, 'new_file')
|
2469
2564
|
assert_nil @hd.load_auto_blocks([])
|
2470
2565
|
end
|
2471
2566
|
|
2472
2567
|
def test_load_auto_blocks_without_block_name
|
2473
2568
|
@hd.instance_variable_set(:@delegate_object, {
|
2474
2569
|
document_load_opts_block_name: nil,
|
2475
|
-
s_most_recent_filename: 'old_file',
|
2476
2570
|
filename: 'new_file'
|
2477
2571
|
})
|
2478
2572
|
assert_nil @hd.load_auto_blocks([])
|
@@ -2645,7 +2739,7 @@ if $PROGRAM_NAME == __FILE__
|
|
2645
2739
|
|
2646
2740
|
def test_wait_for_user_selected_block_with_back_state
|
2647
2741
|
mock_block_state = Struct.new(:state, :block).new(MenuState::BACK,
|
2648
|
-
{
|
2742
|
+
{ oname: 'back_block' })
|
2649
2743
|
@hd.stubs(:wait_for_user_selection).returns(mock_block_state)
|
2650
2744
|
|
2651
2745
|
result = @hd.wait_for_user_selected_block([], ['Block 1', 'Block 2'],
|
@@ -2659,7 +2753,7 @@ if $PROGRAM_NAME == __FILE__
|
|
2659
2753
|
|
2660
2754
|
def test_wait_for_user_selected_block_with_continue_state
|
2661
2755
|
mock_block_state = Struct.new(:state, :block).new(
|
2662
|
-
MenuState::CONTINUE, {
|
2756
|
+
MenuState::CONTINUE, { oname: 'continue_block' }
|
2663
2757
|
)
|
2664
2758
|
@hd.stubs(:wait_for_user_selection).returns(mock_block_state)
|
2665
2759
|
|
data/lib/markdown_exec.rb
CHANGED
@@ -13,10 +13,12 @@ require 'tmpdir'
|
|
13
13
|
require 'tty-prompt'
|
14
14
|
require 'yaml'
|
15
15
|
|
16
|
+
require_relative 'ansi_formatter'
|
16
17
|
require_relative 'block_label'
|
17
18
|
require_relative 'cached_nested_file_reader'
|
18
19
|
require_relative 'cli'
|
19
20
|
require_relative 'colorize'
|
21
|
+
require_relative 'directory_searcher'
|
20
22
|
require_relative 'env'
|
21
23
|
require_relative 'exceptions'
|
22
24
|
require_relative 'fcb'
|
@@ -225,9 +227,9 @@ module MarkdownExec
|
|
225
227
|
|
226
228
|
## Executes the block specified in the options
|
227
229
|
#
|
228
|
-
def execute_block_with_error_handling
|
229
|
-
finalize_cli_argument_processing
|
230
|
-
@options[:
|
230
|
+
def execute_block_with_error_handling
|
231
|
+
finalize_cli_argument_processing
|
232
|
+
@options[:input_cli_rest] = @rest
|
231
233
|
execute_code_block_based_on_options(@options)
|
232
234
|
rescue FileMissingError
|
233
235
|
warn "File missing: #{$!}"
|
@@ -242,6 +244,7 @@ module MarkdownExec
|
|
242
244
|
|
243
245
|
simple_commands = {
|
244
246
|
doc_glob: -> { @fout.fout options[:md_filename_glob] },
|
247
|
+
list_blocks: -> { list_blocks },
|
245
248
|
list_default_yaml: -> { @fout.fout_list list_default_yaml },
|
246
249
|
list_docs: -> { @fout.fout_list files },
|
247
250
|
list_default_env: -> { @fout.fout_list list_default_env },
|
@@ -290,7 +293,7 @@ module MarkdownExec
|
|
290
293
|
|
291
294
|
## post-parse options configuration
|
292
295
|
#
|
293
|
-
def finalize_cli_argument_processing(rest)
|
296
|
+
def finalize_cli_argument_processing(rest = @rest)
|
294
297
|
## position 0: file or folder (optional)
|
295
298
|
#
|
296
299
|
if (pos = rest.shift)&.present?
|
@@ -340,9 +343,8 @@ module MarkdownExec
|
|
340
343
|
end
|
341
344
|
@option_parser.load
|
342
345
|
@option_parser.environment
|
343
|
-
|
344
|
-
|
345
|
-
@options[:s_pass_args] = ARGV[rest.count + 1..]
|
346
|
+
@rest = rest = @option_parser.parse!(arguments_for_mde)
|
347
|
+
@options.pass_args = ARGV[rest.count + 1..]
|
346
348
|
@options.merge(@options.run_state.to_h)
|
347
349
|
|
348
350
|
rest
|
@@ -356,25 +358,64 @@ module MarkdownExec
|
|
356
358
|
def lambda_for_procname(procname, options)
|
357
359
|
case procname
|
358
360
|
when 'debug'
|
359
|
-
|
361
|
+
->(value) {
|
360
362
|
tap_config value: value
|
361
363
|
}
|
362
364
|
when 'exit'
|
363
365
|
->(_) { exit }
|
366
|
+
|
367
|
+
when 'find'
|
368
|
+
->(value) {
|
369
|
+
# initialize_and_parse_cli_options
|
370
|
+
@fout.fout "Searching in: " \
|
371
|
+
"#{HashDelegator.new(@options).string_send_color(@options[:path], :menu_chrome_color)}"
|
372
|
+
searcher = DirectorySearcher.new(value, [@options[:path]])
|
373
|
+
|
374
|
+
@fout.fout 'In directory names'
|
375
|
+
@fout.fout AnsiFormatter.new(options).format_and_highlight_array(
|
376
|
+
searcher.search_in_directory_names, highlight: [value]
|
377
|
+
)
|
378
|
+
|
379
|
+
@fout.fout 'In file names'
|
380
|
+
@fout.fout AnsiFormatter.new(options).format_and_highlight_array(
|
381
|
+
searcher.search_in_file_names, highlight: [value]
|
382
|
+
).join("\n")
|
383
|
+
|
384
|
+
@fout.fout 'In file contents'
|
385
|
+
hash = searcher.search_in_file_contents
|
386
|
+
hash.each.with_index do |(key, v2), i1|
|
387
|
+
@fout.fout format('- %3.d: %s', i1 + 1, key)
|
388
|
+
@fout.fout AnsiFormatter.new(options).format_and_highlight_array(
|
389
|
+
v2.map { |nl| format('=%4.d: %s', nl.index, nl.line) },
|
390
|
+
highlight: [value]
|
391
|
+
)
|
392
|
+
end
|
393
|
+
exit
|
394
|
+
}
|
395
|
+
|
364
396
|
when 'help'
|
365
|
-
|
397
|
+
->(_) {
|
366
398
|
@fout.fout menu_help
|
367
399
|
exit
|
368
400
|
}
|
401
|
+
# when %w[who what where why how which when whom]
|
402
|
+
when 'how'
|
403
|
+
->(value) {
|
404
|
+
# value = 'color'
|
405
|
+
@fout.fout(list_default_yaml.select { |line| line.include? value })
|
406
|
+
exit
|
407
|
+
}
|
369
408
|
when 'path'
|
370
|
-
->(value) {
|
409
|
+
->(value) {
|
410
|
+
read_configuration_file!(options, value)
|
411
|
+
}
|
371
412
|
when 'show_config'
|
372
|
-
|
413
|
+
->(_) {
|
373
414
|
finalize_cli_argument_processing(options)
|
374
415
|
@fout.fout options.sort_by_key.to_yaml
|
375
416
|
}
|
376
417
|
when 'val_as_bool'
|
377
|
-
|
418
|
+
->(value) {
|
378
419
|
value.instance_of?(::String) ? (value.chomp != '0') : value
|
379
420
|
}
|
380
421
|
when 'val_as_int'
|
@@ -391,6 +432,8 @@ module MarkdownExec
|
|
391
432
|
end
|
392
433
|
end
|
393
434
|
|
435
|
+
def list_blocks; end
|
436
|
+
|
394
437
|
def list_default_env
|
395
438
|
menu_iter do |item|
|
396
439
|
next unless item[:env_var].present?
|
@@ -452,17 +495,6 @@ module MarkdownExec
|
|
452
495
|
data.map(&block)
|
453
496
|
end
|
454
497
|
|
455
|
-
def opts_list_files(options)
|
456
|
-
list_files_specified(
|
457
|
-
determine_filename(
|
458
|
-
specified_filename: options[:filename]&.present? ? options[:filename] : nil,
|
459
|
-
specified_folder: options[:path],
|
460
|
-
default_filename: 'README.md',
|
461
|
-
default_folder: '.'
|
462
|
-
)
|
463
|
-
)
|
464
|
-
end
|
465
|
-
|
466
498
|
def menu_export(data = menu_for_optparse)
|
467
499
|
data.map do |item|
|
468
500
|
item.delete(:procname)
|
@@ -484,9 +516,7 @@ module MarkdownExec
|
|
484
516
|
|
485
517
|
# - description and default
|
486
518
|
[item[:description],
|
487
|
-
(if item[:default].present?
|
488
|
-
"[#{value_for_cli item[:default]}]"
|
489
|
-
end)].compact.join(' '),
|
519
|
+
("[#{value_for_cli item[:default]}]" if item[:default].present?)].compact.join(' '),
|
490
520
|
|
491
521
|
# apply proccode, if present, to value
|
492
522
|
# save value to options hash if option is named
|
@@ -499,9 +529,15 @@ module MarkdownExec
|
|
499
529
|
].compact)
|
500
530
|
end
|
501
531
|
|
502
|
-
# Prepares and fetches file listings
|
503
532
|
def opts_prepare_file_list(options)
|
504
|
-
|
533
|
+
list_files_specified(
|
534
|
+
determine_filename(
|
535
|
+
specified_filename: options[:filename]&.present? ? options[:filename] : nil,
|
536
|
+
specified_folder: options[:path],
|
537
|
+
default_filename: 'README.md',
|
538
|
+
default_folder: '.'
|
539
|
+
)
|
540
|
+
)
|
505
541
|
end
|
506
542
|
|
507
543
|
# :reek:UtilityFunction ### temp
|
@@ -516,7 +552,8 @@ module MarkdownExec
|
|
516
552
|
|
517
553
|
def run
|
518
554
|
clear_required_file
|
519
|
-
|
555
|
+
initialize_and_parse_cli_options
|
556
|
+
execute_block_with_error_handling
|
520
557
|
@options.delete_required_temp_file
|
521
558
|
rescue StandardError
|
522
559
|
error_handler('run')
|