markdown_exec 1.7 → 1.8.1
Sign up to get free protection for your applications and to get access to all the features.
- 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')
|