markdown_exec 2.8.4 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +33 -1
- data/Gemfile.lock +1 -1
- data/Rakefile +0 -33
- data/bats/bats.bats +2 -0
- data/bats/block-type-link.bats +1 -1
- data/bats/block-type-ux-allowed.bats +2 -2
- data/bats/block-type-ux-default.bats +8 -0
- data/bats/block-type-ux-invalid.bats +1 -1
- data/bats/{block-type-ux-preconditions.bats → block-type-ux-required-variables.bats} +1 -1
- data/bats/block-type-ux-row-format.bats +1 -1
- data/bats/block-type-ux-sources.bats +36 -0
- data/bats/border.bats +1 -1
- data/bats/cli.bats +2 -2
- data/bats/command-substitution-options.bats +14 -0
- data/bats/command-substitution.bats +1 -1
- data/bats/fail.bats +5 -2
- data/bats/indented-block-type-vars.bats +1 -1
- data/bats/markup.bats +1 -1
- data/bats/option-expansion.bats +8 -0
- data/bats/table-column-truncate.bats +1 -1
- data/bats/test_helper.bash +50 -5
- data/docs/dev/bats-document-configuration.md +1 -1
- data/docs/dev/block-type-ux-allowed.md +5 -5
- data/docs/dev/block-type-ux-auto.md +9 -5
- data/docs/dev/block-type-ux-chained.md +4 -2
- data/docs/dev/block-type-ux-default.md +42 -0
- data/docs/dev/block-type-ux-echo-hash.md +6 -1
- data/docs/dev/block-type-ux-echo.md +3 -1
- data/docs/dev/block-type-ux-exec.md +3 -4
- data/docs/dev/block-type-ux-hidden.md +3 -0
- data/docs/dev/block-type-ux-require.md +9 -18
- data/docs/dev/{block-type-ux-preconditions.md → block-type-ux-required-variables.md} +1 -2
- data/docs/dev/block-type-ux-row-format.md +3 -4
- data/docs/dev/block-type-ux-sources.md +57 -0
- data/docs/dev/block-type-ux-transform.md +0 -4
- data/docs/dev/command-substitution-options.md +61 -0
- data/docs/dev/indented-block-type-vars.md +1 -0
- data/docs/dev/menu-pagination-indent.md +123 -0
- data/docs/dev/menu-pagination.md +111 -0
- data/docs/dev/option-expansion.md +10 -0
- data/lib/ansi_formatter.rb +2 -0
- data/lib/block_cache.rb +197 -0
- data/lib/command_result.rb +57 -0
- data/lib/constants.rb +18 -0
- data/lib/error_reporting.rb +38 -0
- data/lib/evaluate_shell_expressions.rb +43 -18
- data/lib/fcb.rb +114 -11
- data/lib/hash_delegator.rb +595 -359
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/markdown_exec.rb +136 -45
- data/lib/mdoc.rb +74 -23
- data/lib/menu.src.yml +27 -9
- data/lib/menu.yml +23 -8
- data/lib/namer.rb +1 -3
- data/lib/value_or_exception.rb +76 -0
- metadata +18 -4
data/lib/hash_delegator.rb
CHANGED
@@ -22,8 +22,10 @@ require_relative 'array'
|
|
22
22
|
require_relative 'array_util'
|
23
23
|
require_relative 'block_types'
|
24
24
|
require_relative 'cached_nested_file_reader'
|
25
|
+
require_relative 'command_result'
|
25
26
|
require_relative 'constants'
|
26
27
|
require_relative 'directory_searcher'
|
28
|
+
require_relative 'error_reporting'
|
27
29
|
require_relative 'evaluate_shell_expressions'
|
28
30
|
require_relative 'exceptions'
|
29
31
|
require_relative 'fcb'
|
@@ -41,9 +43,11 @@ require_relative 'streams_out'
|
|
41
43
|
require_relative 'string_util'
|
42
44
|
require_relative 'table_extractor'
|
43
45
|
require_relative 'text_analyzer'
|
46
|
+
require_relative 'value_or_exception'
|
44
47
|
|
45
48
|
$pd = false unless defined?($pd)
|
46
49
|
$table_cell_truncate = true
|
50
|
+
EXIT_STATUS_REQUIRED_EMPTY = 248
|
47
51
|
|
48
52
|
module HashDelegatorSelf
|
49
53
|
# Applies an ANSI color method to a string using a specified color key.
|
@@ -138,7 +142,8 @@ module HashDelegatorSelf
|
|
138
142
|
def delete_consecutive_blank_lines!(blocks_menu)
|
139
143
|
blocks_menu.process_and_conditionally_delete! do
|
140
144
|
|prev_item, current_item, _next_item|
|
141
|
-
|
145
|
+
!current_item.is_split? &&
|
146
|
+
prev_item&.fetch(:chrome, nil) &&
|
142
147
|
!(prev_item && prev_item.oname.present?) &&
|
143
148
|
current_item&.fetch(:chrome, nil) &&
|
144
149
|
!(current_item && current_item.oname.present?)
|
@@ -395,9 +400,19 @@ module HashDelegatorSelf
|
|
395
400
|
end
|
396
401
|
|
397
402
|
def persist_fcb_self(all_fcbs, options)
|
398
|
-
|
399
|
-
|
400
|
-
fcb
|
403
|
+
raise if all_fcbs.nil?
|
404
|
+
|
405
|
+
# if the id is present, update the existing fcb
|
406
|
+
if options[:id]
|
407
|
+
fcb = all_fcbs.find { |fcb| fcb.id == options[:id] }
|
408
|
+
if fcb
|
409
|
+
fcb.update(options)
|
410
|
+
return fcb
|
411
|
+
end
|
412
|
+
end
|
413
|
+
MarkdownExec::FCB.new(options).tap do |fcb|
|
414
|
+
all_fcbs << fcb
|
415
|
+
end
|
401
416
|
end
|
402
417
|
end
|
403
418
|
|
@@ -861,30 +876,105 @@ module MarkdownExec
|
|
861
876
|
when :blocks
|
862
877
|
result = SuccessResult.instance
|
863
878
|
if @delegate_object[:bash]
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
879
|
+
begin
|
880
|
+
mf = MenuFilter.new(@delegate_object)
|
881
|
+
if fcb.body.count > 1 && mf.fcb_in_menu?(fcb) && fcb.is_split_displayed?(@delegate_object)
|
882
|
+
# make multiple FCB blocks, one for each line; only the first is active
|
883
|
+
id_prefix = "#{fcb.id}¤BlkFrmNstFls®block:#{count}©body:"
|
884
|
+
fcb0 = fcb
|
885
|
+
menu_lines = fcb.body
|
886
|
+
menu_lines.each.with_index do |menu_line, index|
|
887
|
+
is_enabled_but_inactive = ((index + 1) % (@delegate_object[:select_page_height] / 2)).zero?
|
888
|
+
if index.zero?
|
889
|
+
# fcb.body = [menu_line]
|
890
|
+
# fcb.center = center
|
891
|
+
# fcb.collapse = collapse.nil? ? (line_obj[:collapse] == COLLAPSIBLE_TOKEN_COLLAPSE) : collapse
|
892
|
+
# fcb.disabled = disabled ? TtyMenu::DISABLE : nil
|
893
|
+
fcb.dname = fcb.indent + menu_line
|
894
|
+
fcb.id = "#{id_prefix}#{index}"
|
895
|
+
# fcb.indent = line_obj[:indent]
|
896
|
+
fcb.is_split_first = true # the first block in a split
|
897
|
+
fcb.is_split_rest = false
|
898
|
+
# fcb.level = level
|
899
|
+
# fcb.oname # computed
|
900
|
+
# fcb.s0indent = indent
|
901
|
+
fcb.s0printable = menu_line
|
902
|
+
fcb.s1decorated = menu_line
|
903
|
+
fcb.text = menu_line
|
904
|
+
# fcb.token = line_obj[:collapse]
|
905
|
+
# fcb.type = type
|
906
|
+
else
|
907
|
+
fcb = persist_fcb(
|
908
|
+
body: fcb0.body,
|
909
|
+
center: fcb0.center,
|
910
|
+
chrome: true,
|
911
|
+
collapse: false,
|
912
|
+
disabled: is_enabled_but_inactive ? TtyMenu::ENABLE : TtyMenu::DISABLE,
|
913
|
+
dname: fcb0.indent + menu_line,
|
914
|
+
id: "#{id_prefix}#{index}",
|
915
|
+
indent: fcb0.indent,
|
916
|
+
is_enabled_but_inactive: is_enabled_but_inactive,
|
917
|
+
is_split_first: false,
|
918
|
+
is_split_rest: true, # subsequent blocks in a split
|
919
|
+
level: fcb0.level,
|
920
|
+
s0indent: fcb0.s0indent,
|
921
|
+
s0printable: menu_line,
|
922
|
+
s1decorated: menu_line,
|
923
|
+
start_line: fcb0.start_line,
|
924
|
+
text: menu_line,
|
925
|
+
# token: ,
|
926
|
+
type: fcb0.type
|
927
|
+
)
|
928
|
+
end
|
929
|
+
|
930
|
+
result = fcb.for_menu!(
|
931
|
+
block_calls_scan: @delegate_object[:block_calls_scan],
|
932
|
+
block_name_match: @delegate_object[:block_name_match],
|
933
|
+
block_name_nick_match: @delegate_object[:block_name_nick_match],
|
934
|
+
id: fcb.id,
|
935
|
+
menu_format: @delegate_object[:menu_ux_row_format],
|
936
|
+
prompt: @delegate_object[:prompt_ux_enter_a_value],
|
937
|
+
table_center: @delegate_object[:table_center]
|
938
|
+
) do |oname, color|
|
939
|
+
apply_block_type_color_option(oname, color)
|
940
|
+
end
|
941
|
+
|
942
|
+
results[fcb.id] = result if result.failure?
|
943
|
+
blocks << fcb unless result.failure?
|
944
|
+
end
|
945
|
+
else
|
946
|
+
# prepare block for menu, may fail and call HashDelegator.error_handler
|
947
|
+
result = fcb.for_menu!(
|
948
|
+
block_calls_scan: @delegate_object[:block_calls_scan],
|
949
|
+
block_name_match: @delegate_object[:block_name_match],
|
950
|
+
block_name_nick_match: @delegate_object[:block_name_nick_match],
|
951
|
+
id: fcb.id,
|
952
|
+
menu_format: @delegate_object[:menu_ux_row_format],
|
953
|
+
prompt: @delegate_object[:prompt_ux_enter_a_value],
|
954
|
+
table_center: @delegate_object[:table_center]
|
955
|
+
) do |oname, color|
|
956
|
+
# decorate the displayed line
|
957
|
+
apply_block_type_color_option(oname, color)
|
958
|
+
end
|
959
|
+
results[fcb.id] = result if result.failure?
|
960
|
+
blocks << fcb unless result.failure?
|
961
|
+
end
|
962
|
+
rescue StandardError
|
963
|
+
# ww $@, $!
|
964
|
+
HashDelegator.error_handler('blocks_from_nested_files',
|
965
|
+
{ abort: true })
|
875
966
|
end
|
876
|
-
results[fcb.id] = result if result.failure?
|
877
967
|
else
|
878
968
|
expand_references!(fcb, link_state)
|
969
|
+
blocks << fcb unless result.failure?
|
879
970
|
end
|
880
|
-
blocks << fcb unless result.failure?
|
881
971
|
when :filter # types accepted
|
882
972
|
%i[blocks line]
|
883
973
|
when :line
|
884
974
|
unless @delegate_object[:no_chrome]
|
885
975
|
# expand references only if block is recognized (not a comment)
|
886
976
|
create_and_add_chrome_blocks(
|
887
|
-
blocks, fcb, id: "#{source_id}
|
977
|
+
blocks, fcb, id: "#{source_id}¤BlkFrmNstFls:#{count}®line", init_ids: init_ids
|
888
978
|
) do
|
889
979
|
# expand references only if block is recognized (not a comment)
|
890
980
|
expand_references!(fcb, link_state)
|
@@ -894,7 +984,7 @@ module MarkdownExec
|
|
894
984
|
end
|
895
985
|
OpenStruct.new(blocks: blocks, results: results)
|
896
986
|
rescue StandardError
|
897
|
-
# ww $@,
|
987
|
+
# ww $@, $!
|
898
988
|
HashDelegator.error_handler('blocks_from_nested_files')
|
899
989
|
end
|
900
990
|
|
@@ -907,13 +997,14 @@ module MarkdownExec
|
|
907
997
|
|
908
998
|
def build_replacement_dictionary(
|
909
999
|
commands, link_state,
|
910
|
-
initial_code_required: false,
|
1000
|
+
initial_code_required: false,
|
1001
|
+
occurrence_expressions: nil
|
911
1002
|
)
|
912
1003
|
evaluate_shell_expressions(
|
913
1004
|
(link_state&.inherited_lines_block || ''),
|
914
1005
|
commands,
|
915
1006
|
initial_code_required: initial_code_required,
|
916
|
-
|
1007
|
+
occurrence_expressions: occurrence_expressions
|
917
1008
|
)
|
918
1009
|
end
|
919
1010
|
|
@@ -987,19 +1078,34 @@ module MarkdownExec
|
|
987
1078
|
]
|
988
1079
|
end
|
989
1080
|
|
990
|
-
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
1081
|
+
def code_from_automatic_ux_blocks(
|
1082
|
+
all_blocks,
|
1083
|
+
mdoc
|
1084
|
+
)
|
1085
|
+
unless @ux_most_recent_filename != @delegate_object[:filename]
|
1086
|
+
return
|
1087
|
+
end
|
1088
|
+
|
1089
|
+
blocks = select_automatic_ux_blocks(
|
1090
|
+
all_blocks.reject(&:is_split_rest?)
|
999
1091
|
)
|
1000
|
-
return
|
1092
|
+
return if blocks.empty?
|
1001
1093
|
|
1002
|
-
|
1094
|
+
@ux_most_recent_filename = @delegate_object[:filename]
|
1095
|
+
|
1096
|
+
(blocks.each.with_object([]) do |block, merged_options|
|
1097
|
+
command_result_w_e_t_nl = code_from_ux_block_to_set_environment_variables(
|
1098
|
+
block,
|
1099
|
+
mdoc,
|
1100
|
+
force: @delegate_object[:ux_auto_load_force_default],
|
1101
|
+
only_default: true
|
1102
|
+
)
|
1103
|
+
if command_result_w_e_t_nl.failure?
|
1104
|
+
merged_options
|
1105
|
+
else
|
1106
|
+
merged_options.push(command_result_w_e_t_nl.stdout)
|
1107
|
+
end
|
1108
|
+
end).to_a
|
1003
1109
|
end
|
1004
1110
|
|
1005
1111
|
# parse YAML body defining the UX for a single variable
|
@@ -1017,7 +1123,7 @@ module MarkdownExec
|
|
1017
1123
|
)
|
1018
1124
|
|
1019
1125
|
# process each ux block in sequence, setting ENV and collecting lines
|
1020
|
-
|
1126
|
+
required_lines = []
|
1021
1127
|
required[:blocks].each do |block|
|
1022
1128
|
next unless block.type == BlockType::UX
|
1023
1129
|
|
@@ -1028,35 +1134,51 @@ module MarkdownExec
|
|
1028
1134
|
prompt: @delegate_object[:prompt_ux_enter_a_value],
|
1029
1135
|
validate: '^(?<name>[^ ].*)$'
|
1030
1136
|
)
|
1137
|
+
block.export = export
|
1138
|
+
block.export_act = FCB.act_source(export)
|
1139
|
+
block.export_init = FCB.init_source(export)
|
1031
1140
|
|
1032
|
-
#
|
1141
|
+
# required are variable names that must be set before the UX block is executed.
|
1033
1142
|
# if any precondition is not set, the sequence is aborted.
|
1034
|
-
|
1035
|
-
|
1143
|
+
required_variables = []
|
1144
|
+
export.required&.each do |precondition|
|
1145
|
+
required_variables.push "[[ -z $#{precondition} ]] && exit #{EXIT_STATUS_REQUIRED_EMPTY}"
|
1036
1146
|
end
|
1037
1147
|
|
1148
|
+
eval_code = join_array_of_arrays(
|
1149
|
+
inherited_code, # inherited code
|
1150
|
+
required_lines, # current block requirements
|
1151
|
+
required_variables, # test conditions
|
1152
|
+
required[:code] # current block code
|
1153
|
+
)
|
1038
1154
|
if only_default
|
1039
|
-
|
1040
|
-
ux_block_export_automatic(
|
1041
|
-
|
1042
|
-
|
1155
|
+
command_result_w_e_t_nl =
|
1156
|
+
ux_block_export_automatic(eval_code, export)
|
1157
|
+
# do not display warnings on initializing call
|
1158
|
+
return command_result_w_e_t_nl if command_result_w_e_t_nl.failure?
|
1159
|
+
|
1043
1160
|
else
|
1044
|
-
|
1045
|
-
ux_block_export_activated(
|
1046
|
-
|
1047
|
-
|
1161
|
+
command_result_w_e_t_nl =
|
1162
|
+
ux_block_export_activated(eval_code, export, exit_prompt)
|
1163
|
+
if command_result_w_e_t_nl.failure?
|
1164
|
+
warn command_result_w_e_t_nl.warning if command_result_w_e_t_nl.warning&.present?
|
1165
|
+
return command_result_w_e_t_nl
|
1166
|
+
end
|
1048
1167
|
end
|
1049
|
-
return
|
1168
|
+
return command_result_w_e_t_nl if command_result_w_e_t_nl.failure?
|
1050
1169
|
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1170
|
+
required_lines.concat(command_result_w_e_t_nl.new_lines)
|
1171
|
+
if SelectResponse.continue?(command_result_w_e_t_nl.stdout)
|
1172
|
+
if command_result_w_e_t_nl.transformable
|
1173
|
+
command_result_w_e_t_nl.stdout = transform_export_value(
|
1174
|
+
command_result_w_e_t_nl.stdout, export
|
1175
|
+
)
|
1054
1176
|
end
|
1055
1177
|
|
1056
|
-
if exportable
|
1057
|
-
ENV[export.name] =
|
1058
|
-
|
1059
|
-
|
1178
|
+
if command_result_w_e_t_nl.exportable
|
1179
|
+
ENV[export.name] = command_result_w_e_t_nl.stdout.to_s
|
1180
|
+
required_lines.push code_line_safe_assign(export.name, command_result_w_e_t_nl.stdout,
|
1181
|
+
force: force)
|
1060
1182
|
end
|
1061
1183
|
end
|
1062
1184
|
else
|
@@ -1064,7 +1186,7 @@ module MarkdownExec
|
|
1064
1186
|
end
|
1065
1187
|
end
|
1066
1188
|
|
1067
|
-
|
1189
|
+
CommandResult.new(stdout: required_lines)
|
1068
1190
|
end
|
1069
1191
|
|
1070
1192
|
# sets ENV
|
@@ -1300,7 +1422,9 @@ module MarkdownExec
|
|
1300
1422
|
)
|
1301
1423
|
# Initialize a counter for named group occurrences
|
1302
1424
|
occurrence_count = Hash.new(0)
|
1303
|
-
|
1425
|
+
occurrence_expressions = {}
|
1426
|
+
return [occurrence_count,
|
1427
|
+
occurrence_expressions] if pattern.nil? || pattern == //
|
1304
1428
|
|
1305
1429
|
blocks.each do |block|
|
1306
1430
|
# Skip processing for shell-type blocks
|
@@ -1309,16 +1433,18 @@ module MarkdownExec
|
|
1309
1433
|
# Scan each block name for matches of the pattern
|
1310
1434
|
count_named_group_occurrences_block_body_fix_indent(block).scan(pattern) do |(_, _variable_name)|
|
1311
1435
|
pattern.match($LAST_MATCH_INFO.to_s) # Reapply match for named groups
|
1312
|
-
|
1436
|
+
id = $LAST_MATCH_INFO[group_name]
|
1437
|
+
occurrence_count[id] += 1
|
1438
|
+
occurrence_expressions[id] = $LAST_MATCH_INFO['expression']
|
1313
1439
|
end
|
1314
1440
|
end
|
1315
1441
|
|
1316
|
-
occurrence_count
|
1442
|
+
[occurrence_count, occurrence_expressions]
|
1317
1443
|
end
|
1318
1444
|
|
1319
1445
|
def count_named_group_occurrences_block_body_fix_indent(block)
|
1320
1446
|
### actually double the entries, but not a problem since it's used as a boolean
|
1321
|
-
([block.oname || ''] + block.body).join("\n")
|
1447
|
+
([block.oname || ''] + (block.body || [''])).join("\n")
|
1322
1448
|
end
|
1323
1449
|
|
1324
1450
|
##
|
@@ -1366,9 +1492,9 @@ module MarkdownExec
|
|
1366
1492
|
# split text with newlines, from variable expansion
|
1367
1493
|
if line_cap[:text].include?("\n")
|
1368
1494
|
lines = line_cap[:text].split("\n")
|
1369
|
-
line_caps =
|
1370
|
-
|
1371
|
-
|
1495
|
+
line_caps = lines.map do |line|
|
1496
|
+
line_cap.dup.merge(text: line)
|
1497
|
+
end.to_a
|
1372
1498
|
end
|
1373
1499
|
|
1374
1500
|
# wrap text on multiple lines to screen width, replacing line_caps
|
@@ -1376,7 +1502,7 @@ module MarkdownExec
|
|
1376
1502
|
line_caps = line_caps.flat_map do |line_cap|
|
1377
1503
|
text = line_cap[:text]
|
1378
1504
|
wrapper = StringWrapper.new(width: screen_width_for_wrapping - line_cap[:indent].length)
|
1379
|
-
|
1505
|
+
|
1380
1506
|
if text.length > screen_width_for_wrapping
|
1381
1507
|
# Wrap this text and create line_cap objects for each part
|
1382
1508
|
wrapper.wrap(text).map do |wrapped_text|
|
@@ -1428,17 +1554,17 @@ module MarkdownExec
|
|
1428
1554
|
fcb.center = center
|
1429
1555
|
fcb.chrome = true
|
1430
1556
|
fcb.collapse = collapse.nil? ? (line_obj[:collapse] == COLLAPSIBLE_TOKEN_COLLAPSE) : collapse
|
1431
|
-
fcb.token = line_obj[:collapse]
|
1432
1557
|
fcb.disabled = disabled ? TtyMenu::DISABLE : nil
|
1558
|
+
fcb.dname = line_obj[:indent] + decorated
|
1433
1559
|
fcb.id = "#{id}.#{index}"
|
1560
|
+
fcb.indent = line_obj[:indent]
|
1434
1561
|
fcb.level = level
|
1562
|
+
fcb.oname = line_obj[:text]
|
1435
1563
|
fcb.s0indent = indent
|
1436
1564
|
fcb.s0printable = line_obj[:text]
|
1437
1565
|
fcb.s1decorated = decorated
|
1438
|
-
fcb.dname = line_obj[:indent] + decorated
|
1439
|
-
fcb.indent = line_obj[:indent]
|
1440
|
-
fcb.oname = line_obj[:text]
|
1441
1566
|
fcb.text = line_obj[:text]
|
1567
|
+
fcb.token = line_obj[:collapse]
|
1442
1568
|
fcb.type = type
|
1443
1569
|
use_fcb = false # next line is new record
|
1444
1570
|
else
|
@@ -1446,17 +1572,17 @@ module MarkdownExec
|
|
1446
1572
|
center: center,
|
1447
1573
|
chrome: true,
|
1448
1574
|
collapse: collapse.nil? ? (line_obj[:collapse] == COLLAPSIBLE_TOKEN_COLLAPSE) : collapse,
|
1449
|
-
token: line_obj[:collapse],
|
1450
1575
|
disabled: disabled ? TtyMenu::DISABLE : nil,
|
1576
|
+
dname: line_obj[:indent] + decorated,
|
1451
1577
|
id: "#{id}.#{index}",
|
1578
|
+
indent: line_obj[:indent],
|
1452
1579
|
level: level,
|
1580
|
+
oname: line_obj[:text],
|
1453
1581
|
s0indent: indent,
|
1454
1582
|
s0printable: line_obj[:text],
|
1455
1583
|
s1decorated: decorated,
|
1456
|
-
dname: line_obj[:indent] + decorated,
|
1457
|
-
indent: line_obj[:indent],
|
1458
|
-
oname: line_obj[:text],
|
1459
1584
|
text: line_obj[:text],
|
1585
|
+
token: line_obj[:collapse],
|
1460
1586
|
type: type
|
1461
1587
|
)
|
1462
1588
|
end
|
@@ -1486,7 +1612,9 @@ module MarkdownExec
|
|
1486
1612
|
yield if block_given?
|
1487
1613
|
|
1488
1614
|
# parse multiline to capture output of variable expansion
|
1489
|
-
mbody = fcb.body[0].match Regexp.new(
|
1615
|
+
mbody = fcb.body[0].match Regexp.new(
|
1616
|
+
@delegate_object[criteria[:match]], Regexp::MULTILINE
|
1617
|
+
)
|
1490
1618
|
end
|
1491
1619
|
|
1492
1620
|
create_and_add_chrome_block(
|
@@ -1888,14 +2016,16 @@ module MarkdownExec
|
|
1888
2016
|
|
1889
2017
|
elsif selected.type == BlockType::UX
|
1890
2018
|
debounce_reset
|
2019
|
+
command_result_w_e_t_nl = code_from_ux_block_to_set_environment_variables(
|
2020
|
+
selected,
|
2021
|
+
@dml_mdoc,
|
2022
|
+
inherited_code: @dml_link_state.inherited_lines
|
2023
|
+
)
|
2024
|
+
### TBD if command_result_w_e_t_nl.failure?
|
1891
2025
|
next_state_append_code(
|
1892
2026
|
selected,
|
1893
2027
|
link_state,
|
1894
|
-
|
1895
|
-
selected,
|
1896
|
-
@dml_mdoc,
|
1897
|
-
inherited_code: @dml_link_state.inherited_lines
|
1898
|
-
)
|
2028
|
+
command_result_w_e_t_nl.failure? ? [] : command_result_w_e_t_nl.stdout
|
1899
2029
|
)
|
1900
2030
|
|
1901
2031
|
elsif selected.type == BlockType::VARS
|
@@ -1944,6 +2074,11 @@ module MarkdownExec
|
|
1944
2074
|
@dml_block_state = find_block_state_by_name(block_name)
|
1945
2075
|
dump_and_warn_block_state(name: block_name,
|
1946
2076
|
selected: @dml_block_state.block)
|
2077
|
+
if @dml_block_state.block.fetch(:is_enabled_but_inactive, false)
|
2078
|
+
@dml_block_selection = BlockSelection.new(@dml_block_state.block.id)
|
2079
|
+
return # do nothing
|
2080
|
+
end
|
2081
|
+
|
1947
2082
|
next_block_state =
|
1948
2083
|
execute_block_for_state_and_name(
|
1949
2084
|
selected: @dml_block_state.block,
|
@@ -2117,7 +2252,7 @@ module MarkdownExec
|
|
2117
2252
|
end
|
2118
2253
|
elsif (selected_option = select_option_with_metadata(
|
2119
2254
|
prompt_title,
|
2120
|
-
[exit_prompt] + dirs.map do |file|
|
2255
|
+
[exit_prompt] + dirs.map do |file| # tty_menu_items
|
2121
2256
|
{ name:
|
2122
2257
|
format(
|
2123
2258
|
block_data['view'] || view,
|
@@ -2130,7 +2265,8 @@ module MarkdownExec
|
|
2130
2265
|
oname: file }
|
2131
2266
|
end,
|
2132
2267
|
menu_options.merge(
|
2133
|
-
cycle: true
|
2268
|
+
cycle: true,
|
2269
|
+
match_dml: false
|
2134
2270
|
)
|
2135
2271
|
))
|
2136
2272
|
if selected_option.dname != exit_prompt
|
@@ -2306,9 +2442,9 @@ module MarkdownExec
|
|
2306
2442
|
def execute_inherited_save(
|
2307
2443
|
code_lines: @dml_link_state.inherited_lines
|
2308
2444
|
)
|
2309
|
-
return unless (save_filespec = save_filespec_from_expression
|
2310
|
-
|
2311
|
-
|
2445
|
+
return unless (save_filespec = save_filespec_from_expression(
|
2446
|
+
document_name_in_glob_as_file_name
|
2447
|
+
))
|
2312
2448
|
|
2313
2449
|
unless write_file_with_directory_creation(
|
2314
2450
|
content: HashDelegator.join_code_lines(code_lines),
|
@@ -2364,27 +2500,6 @@ module MarkdownExec
|
|
2364
2500
|
post_execution_process
|
2365
2501
|
end
|
2366
2502
|
|
2367
|
-
def execute_temporary_script(script_code, additional_code = [])
|
2368
|
-
full_code = (additional_code || []) + [script_code]
|
2369
|
-
|
2370
|
-
Tempfile.create('script_exec') do |temp_file|
|
2371
|
-
temp_file.write(HashDelegator.join_code_lines(full_code))
|
2372
|
-
temp_file.flush
|
2373
|
-
File.chmod(0o755, temp_file.path)
|
2374
|
-
|
2375
|
-
output = `#{temp_file.path}`
|
2376
|
-
|
2377
|
-
if $?.exitstatus != 0
|
2378
|
-
return :invalidated
|
2379
|
-
end
|
2380
|
-
|
2381
|
-
output
|
2382
|
-
end
|
2383
|
-
rescue StandardError => err
|
2384
|
-
warn "Error executing script: #{err.message}"
|
2385
|
-
nil
|
2386
|
-
end
|
2387
|
-
|
2388
2503
|
def expand_blocks_with_replacements(
|
2389
2504
|
menu_blocks, replacements, exclude_types: [BlockType::SHELL]
|
2390
2505
|
)
|
@@ -2405,15 +2520,32 @@ module MarkdownExec
|
|
2405
2520
|
def expand_references!(fcb, link_state)
|
2406
2521
|
expand_variable_references!(
|
2407
2522
|
blocks: [fcb],
|
2523
|
+
echo_formatter: method(:format_echo_command),
|
2524
|
+
group_name: :payload,
|
2525
|
+
initial_code_required: false,
|
2526
|
+
link_state: link_state,
|
2527
|
+
pattern: @delegate_object[:option_expansion_expression_regexp].present? &&
|
2528
|
+
Regexp.new(@delegate_object[:option_expansion_expression_regexp])
|
2529
|
+
)
|
2530
|
+
|
2531
|
+
# variable expansions
|
2532
|
+
expand_variable_references!(
|
2533
|
+
blocks: [fcb],
|
2534
|
+
echo_formatter: lambda do |variable|
|
2535
|
+
%(echo "$#{variable}")
|
2536
|
+
end,
|
2537
|
+
group_name: @delegate_object[:variable_expansion_name_capture_group]&.to_sym,
|
2408
2538
|
initial_code_required: false,
|
2409
|
-
link_state: link_state
|
2539
|
+
link_state: link_state,
|
2540
|
+
pattern: options_variable_expansion_regexp
|
2410
2541
|
)
|
2542
|
+
|
2543
|
+
# command substitutions
|
2411
2544
|
expand_variable_references!(
|
2412
2545
|
blocks: [fcb],
|
2413
|
-
|
2414
|
-
group_name: :
|
2546
|
+
echo_formatter: lambda { |command| command },
|
2547
|
+
group_name: @delegate_object[:command_substitution_name_capture_group]&.to_sym,
|
2415
2548
|
initial_code_required: false,
|
2416
|
-
key_format: '$(%s)',
|
2417
2549
|
link_state: link_state,
|
2418
2550
|
pattern: options_command_substitution_regexp
|
2419
2551
|
)
|
@@ -2421,26 +2553,25 @@ module MarkdownExec
|
|
2421
2553
|
|
2422
2554
|
def expand_variable_references!(
|
2423
2555
|
blocks:,
|
2424
|
-
|
2425
|
-
group_name
|
2556
|
+
echo_formatter:,
|
2557
|
+
group_name:,
|
2426
2558
|
initial_code_required: false,
|
2427
|
-
key_format: '${%s}',
|
2428
2559
|
link_state:,
|
2429
|
-
pattern:
|
2560
|
+
pattern:
|
2430
2561
|
)
|
2431
|
-
|
2432
|
-
|
2433
|
-
|
2434
|
-
variable_counts = count_named_group_occurrences(blocks, pattern,
|
2435
|
-
group_name: group_name)
|
2562
|
+
variable_counts, occurrence_expressions = count_named_group_occurrences(
|
2563
|
+
blocks, pattern, group_name: group_name
|
2564
|
+
)
|
2436
2565
|
return if variable_counts.nil? || variable_counts == {}
|
2437
2566
|
|
2438
|
-
echo_commands = generate_echo_commands(
|
2567
|
+
echo_commands = generate_echo_commands(
|
2568
|
+
variable_counts, formatter: echo_formatter
|
2569
|
+
)
|
2439
2570
|
|
2440
2571
|
replacements = build_replacement_dictionary(
|
2441
2572
|
echo_commands, link_state,
|
2442
2573
|
initial_code_required: initial_code_required,
|
2443
|
-
|
2574
|
+
occurrence_expressions: occurrence_expressions
|
2444
2575
|
)
|
2445
2576
|
|
2446
2577
|
return if replacements.nil?
|
@@ -2449,50 +2580,48 @@ module MarkdownExec
|
|
2449
2580
|
expand_blocks_with_replacements(blocks, replacements)
|
2450
2581
|
end
|
2451
2582
|
|
2452
|
-
def
|
2453
|
-
|
2454
|
-
|
2455
|
-
value = execute_temporary_script(
|
2456
|
-
code,
|
2457
|
-
(inherited_code || []) +
|
2458
|
-
code_lines + required[:code]
|
2459
|
-
)
|
2460
|
-
if value == :invalidated
|
2461
|
-
warn "A value must exist for: #{export.preconditions.join(', ')}"
|
2462
|
-
end
|
2463
|
-
value
|
2464
|
-
end
|
2465
|
-
|
2466
|
-
def export_echo_with_code(export, inherited_code, code_lines, required,
|
2467
|
-
force:)
|
2583
|
+
def export_echo_with_code(
|
2584
|
+
bash_script_lines, export, force:
|
2585
|
+
)
|
2468
2586
|
exportable = true
|
2587
|
+
command_result = nil
|
2588
|
+
new_lines = []
|
2469
2589
|
case export.echo
|
2470
|
-
when String
|
2471
|
-
|
2472
|
-
|
2590
|
+
when String, Integer, Float, TrueClass, FalseClass
|
2591
|
+
command_result = output_from_adhoc_bash_script_file(
|
2592
|
+
join_array_of_arrays(
|
2593
|
+
bash_script_lines,
|
2594
|
+
%(printf '%s' "#{export.echo}")
|
2595
|
+
)
|
2596
|
+
)
|
2597
|
+
if command_result.exit_status == EXIT_STATUS_REQUIRED_EMPTY
|
2598
|
+
exportable = false
|
2599
|
+
command_result.warning = warning_required_empty(export)
|
2600
|
+
end
|
2601
|
+
|
2473
2602
|
when Hash
|
2474
2603
|
# each item in the hash is a variable name and value
|
2475
2604
|
export.echo.each do |name, expression|
|
2476
|
-
|
2477
|
-
|
2478
|
-
|
2479
|
-
|
2605
|
+
command_result = output_from_adhoc_bash_script_file(
|
2606
|
+
join_array_of_arrays(
|
2607
|
+
bash_script_lines,
|
2608
|
+
%(printf '%s' "#{expression}")
|
2609
|
+
)
|
2610
|
+
)
|
2611
|
+
if command_result.exit_status == EXIT_STATUS_REQUIRED_EMPTY
|
2612
|
+
command_result.warning = warning_required_empty(export)
|
2613
|
+
else
|
2614
|
+
ENV[name] = command_result.stdout.to_s
|
2615
|
+
new_lines << code_line_safe_assign(name, command_result.stdout,
|
2616
|
+
force: force)
|
2617
|
+
end
|
2480
2618
|
end
|
2619
|
+
|
2620
|
+
# individual items have been exported, none remain
|
2481
2621
|
exportable = false
|
2482
2622
|
end
|
2483
|
-
[value, exportable]
|
2484
|
-
end
|
2485
2623
|
|
2486
|
-
|
2487
|
-
value = execute_temporary_script(
|
2488
|
-
export.exec,
|
2489
|
-
(inherited_code || []) +
|
2490
|
-
code_lines + required[:code]
|
2491
|
-
)
|
2492
|
-
if value == :invalidated
|
2493
|
-
warn "A value must exist for: #{export.preconditions.join(', ')}"
|
2494
|
-
end
|
2495
|
-
value
|
2624
|
+
[command_result, exportable, new_lines]
|
2496
2625
|
end
|
2497
2626
|
|
2498
2627
|
# Retrieves a specific data symbol from the delegate object,
|
@@ -2553,6 +2682,13 @@ module MarkdownExec
|
|
2553
2682
|
)
|
2554
2683
|
end
|
2555
2684
|
|
2685
|
+
def find_option_by_name(name)
|
2686
|
+
name_sym = name.to_sym
|
2687
|
+
@menu_from_yaml.find do |option|
|
2688
|
+
option[:opt_name] == name_sym
|
2689
|
+
end
|
2690
|
+
end
|
2691
|
+
|
2556
2692
|
def format_and_execute_command(
|
2557
2693
|
code_lines:,
|
2558
2694
|
erls:,
|
@@ -2572,6 +2708,24 @@ module MarkdownExec
|
|
2572
2708
|
color_sym: :script_execution_frame_color)
|
2573
2709
|
end
|
2574
2710
|
|
2711
|
+
def format_echo_command(payload)
|
2712
|
+
payload_match = payload.match(@delegate_object[:option_expansion_payload_regexp])
|
2713
|
+
variable = payload_match[:option]
|
2714
|
+
property = payload_match[:property]
|
2715
|
+
|
2716
|
+
echo_value = case property
|
2717
|
+
when 'default', 'description'
|
2718
|
+
item = find_option_by_name(variable)
|
2719
|
+
item ? item[property.to_sym] : ''
|
2720
|
+
when 'length'
|
2721
|
+
@delegate_object[variable.to_sym].to_s.length
|
2722
|
+
else
|
2723
|
+
@delegate_object[variable.to_sym]
|
2724
|
+
end
|
2725
|
+
|
2726
|
+
"echo #{Shellwords.escape(echo_value)}"
|
2727
|
+
end
|
2728
|
+
|
2575
2729
|
# Format expression using environment variables and run state
|
2576
2730
|
def format_expression(expr)
|
2577
2731
|
data = link_load_format_data
|
@@ -2629,13 +2783,12 @@ module MarkdownExec
|
|
2629
2783
|
color_sym: :execution_report_preview_frame_color)
|
2630
2784
|
end
|
2631
2785
|
|
2632
|
-
def generate_echo_commands(variable_counts,
|
2786
|
+
def generate_echo_commands(variable_counts, formatter: nil)
|
2633
2787
|
# commands to echo variables
|
2634
2788
|
#
|
2635
2789
|
commands = {}
|
2636
2790
|
variable_counts.each_key do |variable|
|
2637
|
-
|
2638
|
-
commands[variable] = command
|
2791
|
+
commands[variable] = formatter.call(variable)
|
2639
2792
|
end
|
2640
2793
|
commands
|
2641
2794
|
end
|
@@ -2792,7 +2945,7 @@ module MarkdownExec
|
|
2792
2945
|
|
2793
2946
|
update_line_and_block_state(
|
2794
2947
|
nested_line, state, selected_types,
|
2795
|
-
source_id: "#{@delegate_object[:filename]}
|
2948
|
+
source_id: "#{@delegate_object[:filename]}¤ItrBlkFrmNstFls:#{index}",
|
2796
2949
|
&block
|
2797
2950
|
)
|
2798
2951
|
end
|
@@ -2818,6 +2971,14 @@ module MarkdownExec
|
|
2818
2971
|
end
|
2819
2972
|
end
|
2820
2973
|
|
2974
|
+
# join a list of arrays into a single array
|
2975
|
+
# convert single items to arrays
|
2976
|
+
def join_array_of_arrays(*args)
|
2977
|
+
args.map do |item|
|
2978
|
+
item.is_a?(Array) ? item : [item]
|
2979
|
+
end.compact.flatten(1)
|
2980
|
+
end
|
2981
|
+
|
2821
2982
|
def link_block_data_eval(link_state, code_lines, selected, link_block_data,
|
2822
2983
|
block_source:, shell:)
|
2823
2984
|
all_code = HashDelegator.code_merge(link_state&.inherited_lines,
|
@@ -2943,7 +3104,8 @@ module MarkdownExec
|
|
2943
3104
|
|
2944
3105
|
list = []
|
2945
3106
|
iter_source_blocks(
|
2946
|
-
@delegate_object[:list_blocks_type],
|
3107
|
+
@delegate_object[:list_blocks_type],
|
3108
|
+
source_id: source_id
|
2947
3109
|
) do |block|
|
2948
3110
|
list << (block_eval.present? ? eval(block_eval) : block.send(message))
|
2949
3111
|
end
|
@@ -2952,69 +3114,45 @@ module MarkdownExec
|
|
2952
3114
|
@fout.fout_list(list)
|
2953
3115
|
end
|
2954
3116
|
|
2955
|
-
# Loads auto blocks
|
2956
|
-
#
|
2957
|
-
#
|
2958
|
-
#
|
2959
|
-
#
|
3117
|
+
# Loads and updates auto options for document blocks if the current filename has changed.
|
3118
|
+
#
|
3119
|
+
# This method checks if the delegate object specifies a document load options block name and if the filename
|
3120
|
+
# has been updated. It then selects the appropriate blocks, collects their dependencies, processes their
|
3121
|
+
# options, and updates the menu base with the merged options.
|
3122
|
+
#
|
3123
|
+
# @param all_blocks [Array] An array of all block elements.
|
3124
|
+
# @param mdoc [Object] The document object managing dependencies and options.
|
3125
|
+
# @return [Boolean, nil] Returns true if options were updated; nil otherwise.
|
2960
3126
|
def load_auto_opts_block(all_blocks, mdoc:)
|
2961
|
-
|
2962
|
-
|
2963
|
-
@opts_most_recent_filename != @delegate_object[:filename]
|
2964
|
-
return
|
2965
|
-
end
|
3127
|
+
opts_block_name = @delegate_object[:document_load_opts_block_name]
|
3128
|
+
current_filename = @delegate_object[:filename]
|
2966
3129
|
|
2967
|
-
|
2968
|
-
|
3130
|
+
return unless opts_block_name.present? &&
|
3131
|
+
@opts_most_recent_filename != current_filename
|
2969
3132
|
|
2970
|
-
|
2971
|
-
|
3133
|
+
selected_blocks = HashDelegator.block_select(all_blocks, :oname,
|
3134
|
+
opts_block_name)
|
3135
|
+
return if selected_blocks.empty?
|
3136
|
+
|
3137
|
+
dependency_map = {}
|
3138
|
+
selected_blocks.each do |block|
|
3139
|
+
mdoc.collect_dependencies(memo: dependency_map, block: block)
|
3140
|
+
end
|
3141
|
+
|
3142
|
+
collected_options =
|
3143
|
+
dependency_map.each.with_object({}) do |(block_id, _), merged_options|
|
3144
|
+
matching_block = HashDelegator.block_find(all_blocks, :id, block_id)
|
2972
3145
|
options_state = read_show_options_and_trigger_reuse(
|
2973
|
-
mdoc: mdoc,
|
2974
|
-
selected: block
|
3146
|
+
mdoc: mdoc, selected: matching_block
|
2975
3147
|
)
|
2976
3148
|
merged_options.merge!(options_state.options)
|
2977
3149
|
end
|
2978
|
-
)
|
2979
3150
|
|
2980
|
-
|
3151
|
+
update_menu_base(collected_options)
|
3152
|
+
@opts_most_recent_filename = current_filename
|
2981
3153
|
true
|
2982
3154
|
end
|
2983
3155
|
|
2984
|
-
def load_auto_ux_block(
|
2985
|
-
all_blocks,
|
2986
|
-
mdoc,
|
2987
|
-
block_name: @delegate_object[:document_load_ux_block_name]
|
2988
|
-
)
|
2989
|
-
unless block_name.present? &&
|
2990
|
-
@ux_most_recent_filename != @delegate_object[:filename]
|
2991
|
-
return
|
2992
|
-
end
|
2993
|
-
|
2994
|
-
blocks = HashDelegator.block_select(all_blocks, :oname, block_name)
|
2995
|
-
if blocks.empty?
|
2996
|
-
blocks = HashDelegator.block_match(all_blocks, :nickname,
|
2997
|
-
Regexp.new(block_name))
|
2998
|
-
end
|
2999
|
-
return if blocks.empty?
|
3000
|
-
|
3001
|
-
@ux_most_recent_filename = @delegate_object[:filename]
|
3002
|
-
|
3003
|
-
(blocks.each.with_object([]) do |block, merged_options|
|
3004
|
-
code = code_from_ux_block_to_set_environment_variables(
|
3005
|
-
block,
|
3006
|
-
mdoc,
|
3007
|
-
force: @delegate_object[:ux_auto_load_force_default],
|
3008
|
-
only_default: true
|
3009
|
-
)
|
3010
|
-
if code == :ux_exec_prohibited
|
3011
|
-
merged_options
|
3012
|
-
else
|
3013
|
-
merged_options.push(code)
|
3014
|
-
end
|
3015
|
-
end).to_a
|
3016
|
-
end
|
3017
|
-
|
3018
3156
|
def load_auto_vars_block(
|
3019
3157
|
all_blocks,
|
3020
3158
|
block_name: @delegate_object[:document_load_vars_block_name]
|
@@ -3175,7 +3313,7 @@ module MarkdownExec
|
|
3175
3313
|
|
3176
3314
|
# load document ux block
|
3177
3315
|
#
|
3178
|
-
if (code_lines =
|
3316
|
+
if (code_lines = code_from_automatic_ux_blocks(all_blocks, mdoc))
|
3179
3317
|
new_code = HashDelegator.code_merge(link_state.inherited_lines,
|
3180
3318
|
code_lines)
|
3181
3319
|
next_state_set_code(nil, link_state, new_code)
|
@@ -3231,14 +3369,33 @@ module MarkdownExec
|
|
3231
3369
|
source_id: source_id
|
3232
3370
|
)
|
3233
3371
|
|
3234
|
-
### compress empty lines
|
3235
3372
|
HashDelegator.delete_consecutive_blank_lines!(menu_blocks)
|
3236
|
-
|
3237
|
-
|
3373
|
+
begin
|
3374
|
+
HashDelegator.tables_into_columns!(menu_blocks, @delegate_object,
|
3375
|
+
screen_width_for_table)
|
3376
|
+
rescue NoMethodError
|
3377
|
+
# an invalid table format
|
3378
|
+
end
|
3379
|
+
handle_consecutive_inactive_items!(menu_blocks)
|
3238
3380
|
|
3239
3381
|
[all_blocks, menu_blocks, mdoc]
|
3240
3382
|
end
|
3241
3383
|
|
3384
|
+
def handle_consecutive_inactive_items!(menu_blocks)
|
3385
|
+
consecutive_inactive_count = 0
|
3386
|
+
menu_blocks.each do |fcb|
|
3387
|
+
unless fcb.is_disabled?
|
3388
|
+
consecutive_inactive_count = 0
|
3389
|
+
else
|
3390
|
+
consecutive_inactive_count += 1
|
3391
|
+
if (consecutive_inactive_count % (@delegate_object[:select_page_height] / 3)).zero?
|
3392
|
+
fcb.disabled = TtyMenu::ENABLE
|
3393
|
+
fcb.is_enabled_but_inactive = true
|
3394
|
+
end
|
3395
|
+
end
|
3396
|
+
end
|
3397
|
+
end
|
3398
|
+
|
3242
3399
|
def menu_add_disabled_option(document_glob)
|
3243
3400
|
raise unless document_glob.present?
|
3244
3401
|
raise if @dml_menu_blocks.nil?
|
@@ -3378,9 +3535,9 @@ module MarkdownExec
|
|
3378
3535
|
@delegate_object[:import_paths]&.split(':') || ''
|
3379
3536
|
end
|
3380
3537
|
|
3381
|
-
def
|
3382
|
-
@delegate_object[:
|
3383
|
-
Regexp.new(@delegate_object[:
|
3538
|
+
def options_variable_expansion_regexp
|
3539
|
+
@delegate_object[:variable_expansion_regexp].present? &&
|
3540
|
+
Regexp.new(@delegate_object[:variable_expansion_regexp])
|
3384
3541
|
end
|
3385
3542
|
|
3386
3543
|
def output_color_formatted(data_sym, color_sym)
|
@@ -3405,6 +3562,21 @@ module MarkdownExec
|
|
3405
3562
|
}
|
3406
3563
|
end
|
3407
3564
|
|
3565
|
+
def output_from_adhoc_bash_script_file(bash_script_lines)
|
3566
|
+
Tempfile.create('script_exec') do |temp_file|
|
3567
|
+
temp_file.write(HashDelegator.join_code_lines(bash_script_lines))
|
3568
|
+
temp_file.flush
|
3569
|
+
File.chmod(0o755, temp_file.path)
|
3570
|
+
|
3571
|
+
output = `#{temp_file.path}`
|
3572
|
+
|
3573
|
+
CommandResult.new(stdout: output, exit_status: $?.exitstatus)
|
3574
|
+
end
|
3575
|
+
rescue StandardError => err
|
3576
|
+
warn "Error executing script: #{err.message}"
|
3577
|
+
nil
|
3578
|
+
end
|
3579
|
+
|
3408
3580
|
def output_labeled_value(label, value, level)
|
3409
3581
|
@fout.lout format_references_send_color(
|
3410
3582
|
context: {
|
@@ -3422,9 +3594,7 @@ module MarkdownExec
|
|
3422
3594
|
end
|
3423
3595
|
|
3424
3596
|
def persist_fcb(options)
|
3425
|
-
|
3426
|
-
@fcb_store << fcb
|
3427
|
-
end
|
3597
|
+
HashDelegator.persist_fcb_self(@fcb_store, options)
|
3428
3598
|
end
|
3429
3599
|
|
3430
3600
|
def pop_add_current_code_to_head_and_trigger_load(
|
@@ -3491,6 +3661,11 @@ module MarkdownExec
|
|
3491
3661
|
fout_execution_report if @delegate_object[:output_execution_report]
|
3492
3662
|
end
|
3493
3663
|
|
3664
|
+
# all UX blocks are automatic for the document
|
3665
|
+
def select_automatic_ux_blocks(blocks)
|
3666
|
+
blocks.select { |item| item.type == 'ux' }
|
3667
|
+
end
|
3668
|
+
|
3494
3669
|
# Filter blocks per block_name_include_match, block_name_wrapper_match.
|
3495
3670
|
#
|
3496
3671
|
# @param all_blocks [Array<Hash>] The list of blocks from the file.
|
@@ -4084,7 +4259,7 @@ module MarkdownExec
|
|
4084
4259
|
# Presents a TTY prompt to select an option or exit,
|
4085
4260
|
# returns metadata including option and selected
|
4086
4261
|
def select_option_with_metadata(
|
4087
|
-
prompt_text,
|
4262
|
+
prompt_text, tty_menu_items, opts = {}, menu_blocks: nil
|
4088
4263
|
)
|
4089
4264
|
@dml_menu_blocks = menu_blocks if menu_blocks
|
4090
4265
|
|
@@ -4107,10 +4282,10 @@ module MarkdownExec
|
|
4107
4282
|
per_page: @delegate_object[:select_page_height]
|
4108
4283
|
}.freeze
|
4109
4284
|
|
4110
|
-
if
|
4285
|
+
if tty_menu_items.all? do |item|
|
4111
4286
|
!item.is_a?(String) && item[:disabled]
|
4112
4287
|
end
|
4113
|
-
|
4288
|
+
tty_menu_items.each do |prompt_item|
|
4114
4289
|
puts prompt_item[:dname]
|
4115
4290
|
end
|
4116
4291
|
return
|
@@ -4120,19 +4295,21 @@ module MarkdownExec
|
|
4120
4295
|
# crashes if default is not an existing item
|
4121
4296
|
#
|
4122
4297
|
selection = @prompt.select(prompt_text,
|
4123
|
-
|
4298
|
+
tty_menu_items,
|
4124
4299
|
opts.merge(props))
|
4125
4300
|
rescue TTY::Prompt::ConfigurationError
|
4126
4301
|
# prompt fails when collapsible block name has changed; clear default
|
4127
4302
|
selection = @prompt.select(prompt_text,
|
4128
|
-
|
4303
|
+
tty_menu_items,
|
4129
4304
|
opts.merge(props).merge(default: nil))
|
4130
4305
|
rescue NoMethodError
|
4131
4306
|
# no enabled options in page
|
4132
4307
|
return
|
4133
4308
|
end
|
4134
4309
|
|
4135
|
-
|
4310
|
+
menu_list = opts.fetch(:match_dml, true) ? @dml_menu_blocks : menu_items
|
4311
|
+
menu_list ||= tty_menu_items
|
4312
|
+
selected = menu_list.find do |item|
|
4136
4313
|
if item.instance_of?(Hash)
|
4137
4314
|
[item[:id], item[:name], item[:dname]].include?(selection)
|
4138
4315
|
elsif item.instance_of?(MarkdownExec::FCB)
|
@@ -4142,7 +4319,15 @@ module MarkdownExec
|
|
4142
4319
|
end
|
4143
4320
|
end
|
4144
4321
|
|
4322
|
+
# new FCB if selected is not an object
|
4323
|
+
if selected.instance_of?(String)
|
4324
|
+
selected = FCB.new(dname: selected)
|
4325
|
+
elsif selected.instance_of?(Hash)
|
4326
|
+
selected = FCB.new(selected)
|
4327
|
+
end
|
4328
|
+
|
4145
4329
|
unless selected
|
4330
|
+
report_and_reraise('menu item not found')
|
4146
4331
|
HashDelegator.error_handler('select_option_with_metadata',
|
4147
4332
|
error: 'menu item not found')
|
4148
4333
|
exit 1
|
@@ -4394,145 +4579,188 @@ module MarkdownExec
|
|
4394
4579
|
@delegate_object.merge!(options)
|
4395
4580
|
end
|
4396
4581
|
|
4397
|
-
def ux_block_export_activated(
|
4398
|
-
|
4582
|
+
def ux_block_export_activated(
|
4583
|
+
bash_script_lines, export, exit_prompt
|
4584
|
+
)
|
4399
4585
|
exportable = true
|
4400
4586
|
transformable = true
|
4401
|
-
[
|
4402
|
-
|
4403
|
-
|
4404
|
-
|
4405
|
-
|
4406
|
-
|
4407
|
-
|
4408
|
-
|
4409
|
-
|
4410
|
-
|
4411
|
-
|
4412
|
-
|
4413
|
-
|
4414
|
-
|
4415
|
-
|
4416
|
-
|
4417
|
-
|
4418
|
-
|
4419
|
-
|
4420
|
-
|
4421
|
-
|
4422
|
-
|
4423
|
-
|
4424
|
-
|
4425
|
-
|
4426
|
-
|
4427
|
-
|
4428
|
-
|
4429
|
-
|
4430
|
-
|
4431
|
-
|
4432
|
-
|
4433
|
-
|
4434
|
-
|
4435
|
-
|
4436
|
-
|
4437
|
-
|
4438
|
-
|
4439
|
-
|
4440
|
-
|
4441
|
-
|
4442
|
-
|
4443
|
-
|
4444
|
-
|
4445
|
-
|
4446
|
-
|
4447
|
-
|
4448
|
-
|
4449
|
-
|
4450
|
-
|
4451
|
-
|
4452
|
-
|
4453
|
-
|
4454
|
-
|
4455
|
-
|
4456
|
-
|
4457
|
-
|
4458
|
-
|
4459
|
-
|
4460
|
-
|
4461
|
-
|
4462
|
-
|
4463
|
-
|
4464
|
-
|
4465
|
-
|
4466
|
-
|
4467
|
-
|
4468
|
-
|
4469
|
-
|
4470
|
-
|
4471
|
-
|
4472
|
-
|
4473
|
-
|
4474
|
-
|
4475
|
-
|
4476
|
-
|
4477
|
-
|
4478
|
-
|
4479
|
-
|
4480
|
-
|
4481
|
-
|
4587
|
+
new_lines = []
|
4588
|
+
command_result = nil
|
4589
|
+
|
4590
|
+
case as = FCB.act_source(export)####
|
4591
|
+
when false, UxActSource::FALSE
|
4592
|
+
raise 'Should not be reached.'
|
4593
|
+
|
4594
|
+
when ':allow', UxActSource::ALLOW
|
4595
|
+
raise unless export.allow.present?
|
4596
|
+
|
4597
|
+
case export.allow
|
4598
|
+
when :echo, ExportValueSource::ECHO
|
4599
|
+
command_result, exportable, new_lines = export_echo_with_code(
|
4600
|
+
bash_script_lines,
|
4601
|
+
export,
|
4602
|
+
force: true
|
4603
|
+
)
|
4604
|
+
if command_result.failure?
|
4605
|
+
command_result
|
4606
|
+
else
|
4607
|
+
command_result = CommandResult.new(
|
4608
|
+
stdout: menu_from_list_with_back(command_result.stdout.split("\n"))
|
4609
|
+
)
|
4610
|
+
end
|
4611
|
+
|
4612
|
+
when ':exec', UxActSource::EXEC
|
4613
|
+
command_result = output_from_adhoc_bash_script_file(
|
4614
|
+
join_array_of_arrays(bash_script_lines, export.exec)
|
4615
|
+
)
|
4616
|
+
|
4617
|
+
if command_result.exit_status == EXIT_STATUS_REQUIRED_EMPTY
|
4618
|
+
command_result
|
4619
|
+
else
|
4620
|
+
command_result = CommandResult.new(
|
4621
|
+
stdout: menu_from_list_with_back(
|
4622
|
+
command_result.stdout.split("\n")
|
4623
|
+
)
|
4624
|
+
)
|
4625
|
+
end
|
4626
|
+
|
4627
|
+
else
|
4628
|
+
command_result = CommandResult.new(
|
4629
|
+
stdout: menu_from_list_with_back(export.allow)
|
4630
|
+
)
|
4631
|
+
end
|
4632
|
+
|
4633
|
+
when ':echo', UxActSource::ECHO
|
4634
|
+
command_result, exportable, new_lines = export_echo_with_code(
|
4635
|
+
bash_script_lines,
|
4636
|
+
export,
|
4637
|
+
force: true
|
4638
|
+
)
|
4639
|
+
|
4640
|
+
command_result
|
4641
|
+
|
4642
|
+
when ':edit', UxActSource::EDIT
|
4643
|
+
output = nil
|
4644
|
+
begin
|
4645
|
+
loop do
|
4646
|
+
print "#{export.prompt} [#{export.default}]: "
|
4647
|
+
output = gets.chomp
|
4648
|
+
output = export.default.to_s if output.empty?
|
4649
|
+
caps = NamedCaptureExtractor.extract_named_groups(output,
|
4650
|
+
export.validate)
|
4651
|
+
break if caps
|
4652
|
+
|
4653
|
+
# invalid input, retry
|
4654
|
+
end
|
4655
|
+
rescue Interrupt
|
4656
|
+
exportable = false
|
4657
|
+
transformable = false
|
4658
|
+
end
|
4659
|
+
|
4660
|
+
command_result = CommandResult.new(stdout: output)
|
4661
|
+
|
4662
|
+
when ':exec', UxActSource::EXEC
|
4663
|
+
command_result = output_from_adhoc_bash_script_file(
|
4664
|
+
join_array_of_arrays(bash_script_lines, export.exec)
|
4665
|
+
)
|
4666
|
+
|
4667
|
+
command_result
|
4668
|
+
|
4669
|
+
else
|
4670
|
+
transformable = false
|
4671
|
+
command_result = CommandResult.new(stdout: export.default.to_s)
|
4672
|
+
end
|
4673
|
+
|
4674
|
+
# add message for required variables
|
4675
|
+
if command_result.exit_status == EXIT_STATUS_REQUIRED_EMPTY
|
4676
|
+
command_result.warning = warning_required_empty(export)
|
4677
|
+
# warn command_result.warning
|
4678
|
+
end
|
4679
|
+
|
4680
|
+
command_result.exportable = exportable
|
4681
|
+
command_result.transformable = transformable
|
4682
|
+
command_result.new_lines = new_lines
|
4683
|
+
command_result
|
4684
|
+
end
|
4685
|
+
|
4686
|
+
def ux_block_export_automatic(bash_script_lines, export)
|
4482
4687
|
transformable = true
|
4483
4688
|
exportable = true
|
4484
|
-
[
|
4485
|
-
|
4486
|
-
raise unless export.allowed.present?
|
4689
|
+
new_lines = []
|
4690
|
+
command_result = nil
|
4487
4691
|
|
4488
|
-
|
4489
|
-
|
4490
|
-
|
4491
|
-
|
4492
|
-
|
4692
|
+
case FCB.init_source(export)
|
4693
|
+
when false, UxActSource::FALSE
|
4694
|
+
exportable = false
|
4695
|
+
transformable = false
|
4696
|
+
command_result = CommandResult.new
|
4697
|
+
|
4698
|
+
when ':allow', UxActSource::ALLOW
|
4699
|
+
raise unless export.allow.present?
|
4700
|
+
|
4701
|
+
case export.allow
|
4702
|
+
when :echo, ExportValueSource::ECHO
|
4703
|
+
command_result, exportable, new_lines = export_echo_with_code(
|
4704
|
+
bash_script_lines,
|
4705
|
+
export,
|
4706
|
+
force: false
|
4707
|
+
)
|
4708
|
+
unless command_result.failure?
|
4709
|
+
command_result.stdout = (exportable && command_result.stdout.split("\n").first) || ''
|
4710
|
+
end
|
4493
4711
|
|
4494
|
-
|
4712
|
+
when :exec, ExportValueSource::EXEC
|
4713
|
+
command_result = output_from_adhoc_bash_script_file(
|
4714
|
+
join_array_of_arrays(bash_script_lines, export.exec)
|
4715
|
+
)
|
4716
|
+
unless command_result.failure?
|
4717
|
+
command_result.stdout = command_result.stdout.split("\n").first
|
4718
|
+
end
|
4495
4719
|
|
4496
|
-
|
4497
|
-
|
4498
|
-
|
4499
|
-
|
4720
|
+
else
|
4721
|
+
# must be a list
|
4722
|
+
command_result = CommandResult.new(stdout: export.allow.first)
|
4723
|
+
end
|
4500
4724
|
|
4501
|
-
|
4725
|
+
when ':default', UxActSource::DEFAULT
|
4726
|
+
transformable = false
|
4727
|
+
command_result = CommandResult.new(stdout: export.default.to_s)
|
4502
4728
|
|
4503
|
-
|
4504
|
-
|
4505
|
-
end
|
4729
|
+
when ':echo', UxActSource::ECHO
|
4730
|
+
raise unless export.echo.present?
|
4506
4731
|
|
4507
|
-
|
4508
|
-
|
4509
|
-
|
4732
|
+
command_result, exportable, new_lines = export_echo_with_code(
|
4733
|
+
bash_script_lines,
|
4734
|
+
export,
|
4735
|
+
force: false
|
4736
|
+
)
|
4510
4737
|
|
4511
|
-
|
4512
|
-
|
4513
|
-
)
|
4514
|
-
return :ux_exec_prohibited if output == :invalidated
|
4738
|
+
when ':exec', UxActSource::EXEC
|
4739
|
+
raise unless export.exec.present?
|
4515
4740
|
|
4516
|
-
|
4741
|
+
command_result = output_from_adhoc_bash_script_file(
|
4742
|
+
join_array_of_arrays(bash_script_lines, export.exec)
|
4743
|
+
)
|
4517
4744
|
|
4518
|
-
|
4519
|
-
|
4520
|
-
|
4745
|
+
else
|
4746
|
+
command_result = CommandResult.new(stdout: export.init.to_s)
|
4747
|
+
# raise "Unknown FCB.init_source(export) #{FCB.init_source(export)}"
|
4748
|
+
end
|
4521
4749
|
|
4522
|
-
|
4523
|
-
|
4524
|
-
|
4525
|
-
|
4750
|
+
# add message for required variables
|
4751
|
+
if command_result.exit_status == EXIT_STATUS_REQUIRED_EMPTY
|
4752
|
+
command_result.warning = warning_required_empty(export)
|
4753
|
+
warn command_result.warning
|
4754
|
+
end
|
4526
4755
|
|
4527
|
-
|
4756
|
+
command_result.exportable = exportable
|
4757
|
+
command_result.transformable = transformable
|
4758
|
+
command_result.new_lines = new_lines
|
4759
|
+
command_result
|
4760
|
+
end
|
4528
4761
|
|
4529
|
-
|
4530
|
-
|
4531
|
-
transformable = false
|
4532
|
-
export.default.to_s
|
4533
|
-
end,
|
4534
|
-
exportable,
|
4535
|
-
transformable]
|
4762
|
+
def warning_required_empty(export)
|
4763
|
+
"A value must exist for: #{export.required.join(', ')}"
|
4536
4764
|
end
|
4537
4765
|
|
4538
4766
|
def vux_await_user_selection(prior_answer: @dml_block_selection)
|
@@ -4759,7 +4987,8 @@ module MarkdownExec
|
|
4759
4987
|
#
|
4760
4988
|
# @return [Nil] Returns nil if no code block is selected
|
4761
4989
|
# or an error occurs.
|
4762
|
-
def vux_main_loop
|
4990
|
+
def vux_main_loop(menu_from_yaml: nil)
|
4991
|
+
@menu_from_yaml = menu_from_yaml
|
4763
4992
|
vux_init
|
4764
4993
|
vux_load_code_files_into_state
|
4765
4994
|
formatted_choice_ostructs = vux_formatted_names_for_state_chrome_blocks
|
@@ -4809,10 +5038,10 @@ module MarkdownExec
|
|
4809
5038
|
case msg
|
4810
5039
|
when :parse_document # once for each menu
|
4811
5040
|
count = 0
|
4812
|
-
vux_parse_document(source_id: "#{@delegate_object[:filename]}
|
5041
|
+
vux_parse_document(source_id: "#{@delegate_object[:filename]}¤VuxMainLoop®PrsDoc")
|
4813
5042
|
vux_menu_append_history_files(
|
4814
5043
|
formatted_choice_ostructs,
|
4815
|
-
source_id: "#{@delegate_object[:filename]}
|
5044
|
+
source_id: "#{@delegate_object[:filename]}¤VuxMainLoop®HstFls"
|
4816
5045
|
)
|
4817
5046
|
vux_publish_document_file_name_for_external_automation
|
4818
5047
|
|
@@ -4824,7 +5053,7 @@ module MarkdownExec
|
|
4824
5053
|
# yield :end_of_cli, @delegate_object
|
4825
5054
|
|
4826
5055
|
if @delegate_object[:list_blocks]
|
4827
|
-
list_blocks(source_id: "#{@delegate_object[:filename]}
|
5056
|
+
list_blocks(source_id: "#{@delegate_object[:filename]}¤VuxMainLoop®EndCLI")
|
4828
5057
|
:exit
|
4829
5058
|
end
|
4830
5059
|
|
@@ -5049,8 +5278,8 @@ module MarkdownExec
|
|
5049
5278
|
:prompt_color_after_script_execution
|
5050
5279
|
)
|
5051
5280
|
|
5052
|
-
|
5053
|
-
if
|
5281
|
+
tty_menu_items = blocks_as_menu_items(menu_blocks)
|
5282
|
+
if tty_menu_items.empty?
|
5054
5283
|
return SelectedBlockMenuState.new(nil, OpenStruct.new,
|
5055
5284
|
MenuState::EXIT)
|
5056
5285
|
end
|
@@ -5085,8 +5314,9 @@ module MarkdownExec
|
|
5085
5314
|
{ cycle: @delegate_object[:select_page_cycle],
|
5086
5315
|
per_page: @delegate_object[:select_page_height] }
|
5087
5316
|
)
|
5088
|
-
selected_option = select_option_with_metadata(
|
5089
|
-
|
5317
|
+
selected_option = select_option_with_metadata(
|
5318
|
+
prompt_title, tty_menu_items, selection_opts
|
5319
|
+
)
|
5090
5320
|
determine_block_state(selected_option)
|
5091
5321
|
end
|
5092
5322
|
|
@@ -5160,9 +5390,13 @@ module MarkdownExec
|
|
5160
5390
|
save_expr = link_block_data.fetch(LinkKeys::SAVE, '')
|
5161
5391
|
if save_expr.present?
|
5162
5392
|
save_filespec = save_filespec_from_expression(save_expr)
|
5163
|
-
|
5164
|
-
|
5165
|
-
|
5393
|
+
if save_filespec.present?
|
5394
|
+
File.write(save_filespec,
|
5395
|
+
HashDelegator.join_code_lines(link_state&.inherited_lines))
|
5396
|
+
@delegate_object[:filename]
|
5397
|
+
else
|
5398
|
+
link_block_data[LinkKeys::FILE] || @delegate_object[:filename]
|
5399
|
+
end
|
5166
5400
|
else
|
5167
5401
|
link_block_data[LinkKeys::FILE] || @delegate_object[:filename]
|
5168
5402
|
end
|
@@ -5170,6 +5404,8 @@ module MarkdownExec
|
|
5170
5404
|
end
|
5171
5405
|
|
5172
5406
|
class HashDelegator < HashDelegatorParent
|
5407
|
+
include ::ErrorReporting
|
5408
|
+
|
5173
5409
|
# Cleans a value, handling both Hash and Struct types.
|
5174
5410
|
# For Structs, the cleaned version is converted to a hash.
|
5175
5411
|
def self.clean_value(value)
|
@@ -5382,7 +5618,7 @@ module MarkdownExec
|
|
5382
5618
|
input: MarkdownExec::FCB.new(title: '',
|
5383
5619
|
body: ['def add(x, y)',
|
5384
5620
|
' x + y', 'end']),
|
5385
|
-
output: "def add(x, y)\n x + y\n end
|
5621
|
+
output: "def add(x, y)\n x + y\n end"
|
5386
5622
|
},
|
5387
5623
|
{
|
5388
5624
|
input: MarkdownExec::FCB.new(title: 'foo', body: %w[bar baz]),
|