markdown_exec 3.1.1 → 3.3.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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/Gemfile.lock +1 -1
  4. data/Rakefile +3 -3
  5. data/bats/block-type-ux-auto.bats +1 -1
  6. data/bats/block-type-ux-default.bats +1 -1
  7. data/bats/block-type-ux-echo-hash-transform.bats +1 -1
  8. data/bats/block-type-ux-echo-hash.bats +2 -2
  9. data/bats/block-type-ux-exec-hash-transform.bats +8 -0
  10. data/bats/block-type-ux-exec-hash.bats +15 -0
  11. data/bats/block-type-ux-exec.bats +1 -1
  12. data/bats/block-type-ux-force.bats +9 -0
  13. data/bats/block-type-ux-formats.bats +8 -0
  14. data/bats/block-type-ux-readonly.bats +1 -1
  15. data/bats/block-type-ux-row-format.bats +1 -1
  16. data/bats/block-type-ux-transform.bats +1 -1
  17. data/bats/import-directive-parameter-symbols.bats +9 -0
  18. data/bats/import-duplicates.bats +4 -2
  19. data/bats/import-parameter-symbols.bats +8 -0
  20. data/bats/markup.bats +1 -1
  21. data/bats/options.bats +1 -1
  22. data/bin/tab_completion.sh +5 -1
  23. data/docs/dev/block-type-ux-echo-hash-transform.md +14 -12
  24. data/docs/dev/block-type-ux-exec-hash-transform.md +37 -0
  25. data/docs/dev/block-type-ux-exec-hash.md +93 -0
  26. data/docs/dev/block-type-ux-force.md +20 -0
  27. data/docs/dev/block-type-ux-formats.md +58 -0
  28. data/docs/dev/hexdump_format.md +267 -0
  29. data/docs/dev/import/parameter-symbols.md +6 -0
  30. data/docs/dev/import-directive-parameter-symbols.md +9 -0
  31. data/docs/dev/import-parameter-symbols-template.md +24 -0
  32. data/docs/dev/import-parameter-symbols.md +6 -0
  33. data/docs/dev/load-vars-state-demo.md +35 -0
  34. data/docs/ux-blocks-examples.md +3 -3
  35. data/examples/import_with_substitution_demo.md +130 -26
  36. data/examples/imports/organism_template.md +86 -29
  37. data/lib/cached_nested_file_reader.rb +265 -27
  38. data/lib/constants.rb +8 -1
  39. data/lib/env_interface.rb +13 -7
  40. data/lib/evaluate_shell_expressions.rb +1 -0
  41. data/lib/fcb.rb +123 -19
  42. data/lib/format_table.rb +56 -23
  43. data/lib/fout.rb +5 -0
  44. data/lib/hash_delegator.rb +1196 -337
  45. data/lib/markdown_exec/version.rb +2 -1
  46. data/lib/markdown_exec.rb +2 -0
  47. data/lib/mdoc.rb +12 -6
  48. data/lib/menu.src.yml +150 -33
  49. data/lib/menu.yml +126 -31
  50. data/lib/string_util.rb +80 -0
  51. data/lib/table_extractor.rb +170 -64
  52. data/lib/ww.rb +328 -30
  53. metadata +18 -2
@@ -97,6 +97,33 @@ module HashDelegatorSelf
97
97
  blocks.select { |item| item.send(msg) == value }
98
98
  end
99
99
 
100
+ def chrome_block_criteria
101
+ [
102
+ { center: :table_center, format: :menu_note_format,
103
+ match: :table_row_multi_line_match, type: BlockType::TEXT },
104
+ { case_conversion: :upcase, center: :heading1_center,
105
+ collapse: :heading1_collapse, collapsible: :heading1_collapsible,
106
+ color: :menu_heading1_color, format: :menu_heading1_format, level: 1,
107
+ match: :heading1_match, type: BlockType::HEADING, wrap: true },
108
+ { center: :heading2_center,
109
+ collapse: :heading2_collapse, collapsible: :heading2_collapsible,
110
+ color: :menu_heading2_color, format: :menu_heading2_format, level: 2,
111
+ match: :heading2_match, type: BlockType::HEADING, wrap: true },
112
+ { case_conversion: :downcase, center: :heading3_center,
113
+ collapse: :heading3_collapse, collapsible: :heading3_collapsible,
114
+ color: :menu_heading3_color, format: :menu_heading3_format, level: 3,
115
+ match: :heading3_match, type: BlockType::HEADING, wrap: true },
116
+ { center: :divider4_center,
117
+ collapse: :divider4_collapse, collapsible: :divider4_collapsible,
118
+ color: :menu_divider_color, format: :menu_divider_format, level: 4,
119
+ match: :divider_match, type: BlockType::DIVIDER },
120
+ { color: :menu_note_color, format: :menu_note_format,
121
+ match: :menu_note_match, type: BlockType::TEXT, wrap: true },
122
+ { color: :menu_task_color, format: :menu_task_format,
123
+ match: :menu_task_match, type: BlockType::TEXT, wrap: true }
124
+ ]
125
+ end
126
+
100
127
  def count_matches_in_lines(lines, regex)
101
128
  lines.count { |line| line.to_s.match(regex) }
102
129
  end
@@ -274,7 +301,9 @@ module HashDelegatorSelf
274
301
  lines = blocks_menu.map(&:oname)
275
302
  text_tables = TableExtractor.extract_tables(
276
303
  lines,
277
- regexp: delegate_object[:table_parse_regexp]
304
+ regexp: delegate_object[:table_parse_regexp],
305
+ multi_line_delimiter: delegate_object[:table_row_multi_line_delimiter],
306
+ single_line_delimiter: delegate_object[:table_row_single_line_delimiter]
278
307
  )
279
308
  return unless text_tables.count.positive?
280
309
 
@@ -287,11 +316,16 @@ module HashDelegatorSelf
287
316
  column_count: table[:columns],
288
317
  decorate: {
289
318
  border: delegate_object[:table_border_color],
290
- header_row: delegate_object[:table_header_row_color],
319
+ header_row: if table[:rows] == 1
320
+ delegate_object[:table_row_color]
321
+ else
322
+ delegate_object[:table_header_row_color]
323
+ end,
291
324
  row: delegate_object[:table_row_color],
292
325
  separator_line: delegate_object[:table_separator_line_color]
293
326
  },
294
327
  lines: lines,
328
+ table: table,
295
329
  table_width: screen_width_for_table,
296
330
  truncate: $table_cell_truncate
297
331
  )
@@ -390,11 +424,22 @@ module HashDelegatorSelf
390
424
  # @param [String] line The line to be processed.
391
425
  # @param [Array<Symbol>] selected_types A list of message types to check.
392
426
  # @param [Proc] block The block to be called with the line data.
393
- def yield_line_if_selected(line, selected_types, all_fcbs: nil,
394
- source_id: '', &block)
427
+ def yield_line_if_selected(
428
+ line, selected_types, all_fcbs: nil,
429
+ criteria: nil, source_id: '', &block
430
+ )
395
431
  return unless block && block_type_selected?(selected_types, :line)
396
432
 
397
- block.call(:line, persist_fcb_self(all_fcbs, body: [line], id: source_id))
433
+ opts = {
434
+ body: [line],
435
+ id: source_id
436
+ }
437
+ # add style if it is a single line table
438
+ opts[:criteria] = criteria if criteria
439
+
440
+ block.call(:line, persist_fcb_self(all_fcbs, opts))
441
+ rescue StandardError
442
+ wwe 'YAML loading error', { body: body, error: $! }
398
443
  end
399
444
 
400
445
  def persist_fcb_self(all_fcbs, options)
@@ -626,6 +671,8 @@ module MarkdownExec
626
671
 
627
672
  @compressed_ids = {}
628
673
  @expanded_ids = {}
674
+ rescue StandardError
675
+ wwe $!
629
676
  end
630
677
 
631
678
  ##
@@ -709,6 +756,22 @@ module MarkdownExec
709
756
  )
710
757
  end
711
758
 
759
+ def annotate_required_lines(name, lines, block_name:)
760
+ if @delegate_object[:required_lines_with_source_comments] && !lines.empty?
761
+ formatted = formatted_block_name(block_name,
762
+ :script_comment_block_name_format)
763
+ ['',
764
+ "#‡¯¯¯ #{name} ¯¯¯ #{formatted}",
765
+ '',
766
+ *lines,
767
+ '',
768
+ "#‡___ #{name} ___ #{formatted}",
769
+ '']
770
+ else
771
+ lines || []
772
+ end
773
+ end
774
+
712
775
  # Appends a chrome block, which is a menu option for Back or Exit
713
776
  #
714
777
  # @param all_blocks [Array] The current blocks in the menu
@@ -819,6 +882,7 @@ module MarkdownExec
819
882
  # @param block_type_color_option [Symbol, nil] The shell color option to apply.
820
883
  # @return [String] The colorized or original name string.
821
884
  def apply_block_type_color_option(name, block_type_color_option)
885
+ ### accept string for color
822
886
  if block_type_color_option && @delegate_object[block_type_color_option].present?
823
887
  string_send_color(name, block_type_color_option)
824
888
  else
@@ -866,6 +930,7 @@ module MarkdownExec
866
930
  results = {}
867
931
  iter_blocks_from_nested_files do |btype, fcb|
868
932
  count += 1
933
+ wwt :iter, 'count:', count, 'btype:', btype, 'fcb.id:', fcb&.id
869
934
  case btype
870
935
  when :blocks
871
936
  result = SuccessResult.instance
@@ -903,7 +968,11 @@ module MarkdownExec
903
968
  center: fcb0.center,
904
969
  chrome: true,
905
970
  collapse: false,
906
- disabled: is_enabled_but_inactive ? TtyMenu::ENABLE : TtyMenu::DISABLE,
971
+ disabled: if is_enabled_but_inactive
972
+ TtyMenu::ENABLE
973
+ else
974
+ TtyMenu::DISABLE
975
+ end,
907
976
  dname: fcb0.indent + menu_line,
908
977
  id: "#{id_prefix}#{index}",
909
978
  indent: fcb0.indent,
@@ -922,9 +991,11 @@ module MarkdownExec
922
991
  end
923
992
 
924
993
  result = fcb.for_menu!(
994
+ appopts: @delegate_object,
925
995
  block_calls_scan: @delegate_object[:block_calls_scan],
926
996
  block_name_match: @delegate_object[:block_name_match],
927
- block_name_nick_match: @delegate_object[:block_name_nick_match],
997
+ block_name_nick_match:
998
+ @delegate_object[:block_name_nick_match],
928
999
  id: fcb.id,
929
1000
  menu_format: @delegate_object[:menu_ux_row_format],
930
1001
  prompt: @delegate_object[:prompt_ux_enter_a_value],
@@ -939,9 +1010,11 @@ module MarkdownExec
939
1010
  else
940
1011
  # prepare block for menu, may fail and call HashDelegator.error_handler
941
1012
  result = fcb.for_menu!(
1013
+ appopts: @delegate_object,
942
1014
  block_calls_scan: @delegate_object[:block_calls_scan],
943
1015
  block_name_match: @delegate_object[:block_name_match],
944
- block_name_nick_match: @delegate_object[:block_name_nick_match],
1016
+ block_name_nick_match:
1017
+ @delegate_object[:block_name_nick_match],
945
1018
  id: fcb.id,
946
1019
  menu_format: @delegate_object[:menu_ux_row_format],
947
1020
  prompt: @delegate_object[:prompt_ux_enter_a_value],
@@ -978,7 +1051,7 @@ module MarkdownExec
978
1051
  end
979
1052
  OpenStruct.new(blocks: blocks, results: results)
980
1053
  rescue StandardError
981
- wwe 'link_state:', link_state, 'source_id:', source_id, 'btype:', btype,
1054
+ wwe $!, 'link_state:', link_state, 'source_id:', source_id, 'btype:', btype,
982
1055
  'fcb:', fcb
983
1056
  end
984
1057
 
@@ -1027,7 +1100,28 @@ module MarkdownExec
1027
1100
 
1028
1101
  def cfile
1029
1102
  @cfile ||= CachedNestedFileReader.new(
1030
- import_pattern: @delegate_object.fetch(:import_pattern) #, "^ *@import +(?<name>.+?) *$")
1103
+ import_directive_line_pattern:
1104
+ @delegate_object.fetch(:import_directive_line_pattern),
1105
+ import_directive_parameter_scan:
1106
+ Regexp.new(
1107
+ @delegate_object.fetch(:import_directive_parameter_scan, '')
1108
+ ),
1109
+ import_parameter_variable_assignment:
1110
+ @delegate_object[:import_parameter_variable_assignment],
1111
+ shell:
1112
+ @delegate_object[:shell],
1113
+ shell_block_name:
1114
+ @delegate_object[:document_load_shell_block_name],
1115
+ symbol_command_substitution:
1116
+ @delegate_object[:import_symbol_command_substitution],
1117
+ symbol_evaluated_expression:
1118
+ @delegate_object[:import_symbol_evaluated_expression],
1119
+ symbol_force_quoted_literal:
1120
+ @delegate_object[:import_symbol_force_quoted_literal],
1121
+ symbol_raw_literal:
1122
+ @delegate_object[:import_symbol_raw_literal],
1123
+ symbol_variable_reference:
1124
+ @delegate_object[:import_symbol_variable_reference]
1031
1125
  )
1032
1126
  end
1033
1127
 
@@ -1045,48 +1139,130 @@ module MarkdownExec
1045
1139
  true
1046
1140
  end
1047
1141
 
1048
- def chrome_block_criteria
1049
- [
1050
- { center: :table_center, format: :menu_note_format,
1051
- match: :menu_table_rows_match, type: BlockType::TEXT },
1052
- { case_conversion: :upcase, center: :heading1_center,
1053
- collapse: :heading1_collapse, collapsible: :heading1_collapsible,
1054
- color: :menu_heading1_color, format: :menu_heading1_format, level: 1,
1055
- match: :heading1_match, type: BlockType::HEADING, wrap: true },
1056
- { center: :heading2_center,
1057
- collapse: :heading2_collapse, collapsible: :heading2_collapsible,
1058
- color: :menu_heading2_color, format: :menu_heading2_format, level: 2,
1059
- match: :heading2_match, type: BlockType::HEADING, wrap: true },
1060
- { case_conversion: :downcase, center: :heading3_center,
1061
- collapse: :heading3_collapse, collapsible: :heading3_collapsible,
1062
- color: :menu_heading3_color, format: :menu_heading3_format, level: 3,
1063
- match: :heading3_match, type: BlockType::HEADING, wrap: true },
1064
- { center: :divider4_center,
1065
- collapse: :divider4_collapse, collapsible: :divider4_collapsible,
1066
- color: :menu_divider_color, format: :menu_divider_format, level: 4,
1067
- match: :divider_match, type: BlockType::DIVIDER },
1068
- { color: :menu_note_color, format: :menu_note_format,
1069
- match: :menu_note_match, type: BlockType::TEXT, wrap: true },
1070
- { color: :menu_task_color, format: :menu_task_format,
1071
- match: :menu_task_match, type: BlockType::TEXT, wrap: true }
1072
- ]
1142
+ # return code resulting from evaluating all automatic blocks in order
1143
+ def code_from_auto_blocks(all_blocks, mdoc: nil, default_only: true)
1144
+ shell_block_name = @delegate_object[:document_load_shell_block_name]
1145
+ vars_block_name = @delegate_object[:document_load_vars_block_name]
1146
+
1147
+ # do not reload the same document
1148
+ read_shell = @shell_most_recent_filename != @delegate_object[:filename]
1149
+ read_ux = @ux_most_recent_filename != @delegate_object[:filename]
1150
+ read_vars = @vars_most_recent_filename != @delegate_object[:filename]
1151
+ @shell_most_recent_filename = @delegate_object[:filename]
1152
+ @ux_most_recent_filename = @delegate_object[:filename]
1153
+ @vars_most_recent_filename = @delegate_object[:filename]
1154
+
1155
+ all_code = []
1156
+ all_blocks.each do |fcb|
1157
+ block_code = []
1158
+ if fcb.oname == shell_block_name
1159
+ if read_shell
1160
+ # collect code from shell block
1161
+ code = if mdoc
1162
+ mdoc.collect_recursively_required_code(
1163
+ anyname: fcb.id,
1164
+ ### anyname: fcb.pub_name,
1165
+ label_format_above:
1166
+ @delegate_object[:shell_code_label_format_above],
1167
+ label_format_below:
1168
+ @delegate_object[:shell_code_label_format_below],
1169
+ block_source: block_source
1170
+ )[:code]
1171
+ else
1172
+ fcb.body
1173
+ end
1174
+ block_code = annotate_required_lines('blk:SHELL', code,
1175
+ block_name: fcb.id)
1176
+ end
1177
+ elsif fcb.oname == vars_block_name
1178
+ if read_vars
1179
+ block_code = code_from_vars_block_to_set_environment_variables(fcb)
1180
+ end
1181
+ elsif fcb.type == 'ux'
1182
+ if !read_ux
1183
+ # skip
1184
+ elsif fcb.is_split_rest?
1185
+ # ignore
1186
+ else
1187
+ command_result_w_e_t_nl =
1188
+ code_from_ux_block_to_set_environment_variables(
1189
+ fcb,
1190
+ mdoc,
1191
+ force: @delegate_object[:ux_auto_load_force_default],
1192
+ only_default: default_only,
1193
+ silent: true
1194
+ )
1195
+ block_code = if command_result_w_e_t_nl&.success?
1196
+ command_result_w_e_t_nl.new_lines
1197
+ else
1198
+ []
1199
+ end
1200
+ end
1201
+
1202
+ else
1203
+ # do nothing
1204
+ end
1205
+
1206
+ wwt :code, 'block_code:', block_code unless block_code&.empty?
1207
+ all_code += block_code
1208
+ end
1209
+
1210
+ all_code.tap { wwt :code, _1 }
1211
+ rescue StandardError
1212
+ wwe 'all_blocks.count:', all_blocks.count
1073
1213
  end
1074
1214
 
1075
- def code_from_automatic_ux_blocks(
1215
+ # return code resulting from evaluating all automatic SHELL blocks in order
1216
+ def code_from_auto_shell_blocks(all_blocks, mdoc: nil)
1217
+ # a block name is required
1218
+ # do not reload the most recent filename
1219
+ block_name = @delegate_object[:document_load_shell_block_name]
1220
+ unless block_name.present? &&
1221
+ @shell_most_recent_filename != @delegate_object[:filename]
1222
+ return
1223
+ end
1224
+
1225
+ @shell_most_recent_filename = @delegate_object[:filename]
1226
+
1227
+ # select the first block with the given name
1228
+ fcb = HashDelegator.block_find(all_blocks, :oname, block_name)
1229
+ return unless fcb
1230
+
1231
+ # collect code from shell block
1232
+ code = if mdoc
1233
+ mdoc.collect_recursively_required_code(
1234
+ anyname: fcb.pub_name,
1235
+ label_format_above:
1236
+ @delegate_object[:shell_code_label_format_above],
1237
+ label_format_below:
1238
+ @delegate_object[:shell_code_label_format_below],
1239
+ block_source: block_source
1240
+ )[:code]
1241
+ else
1242
+ fcb.body
1243
+ end
1244
+ annotate_required_lines('blk:SHELL', code, block_name: fcb.id)
1245
+ end
1246
+
1247
+ # return code resulting from evaluating all UX blocks in order
1248
+ def code_from_auto_ux_blocks(
1076
1249
  all_blocks,
1077
1250
  mdoc
1078
1251
  )
1252
+ # do not reload the most recent filename
1079
1253
  unless @ux_most_recent_filename != @delegate_object[:filename]
1080
1254
  return
1081
1255
  end
1082
1256
 
1257
+ @ux_most_recent_filename = @delegate_object[:filename]
1258
+
1259
+ # select all UX blocks, rejecting non-primary split
1083
1260
  blocks = select_automatic_ux_blocks(
1084
1261
  all_blocks.reject(&:is_split_rest?)
1085
1262
  )
1086
1263
  return if blocks.empty?
1087
1264
 
1088
- @ux_most_recent_filename = @delegate_object[:filename]
1089
-
1265
+ # collect code from all ux blocks
1090
1266
  (blocks.each.with_object([]) do |block, merged_options|
1091
1267
  command_result_w_e_t_nl =
1092
1268
  code_from_ux_block_to_set_environment_variables(
@@ -1097,6 +1273,7 @@ module MarkdownExec
1097
1273
  silent: true
1098
1274
  )
1099
1275
  if command_result_w_e_t_nl.failure?
1276
+ # if a block fails, proceed with next block
1100
1277
  merged_options
1101
1278
  else
1102
1279
  merged_options.push(command_result_w_e_t_nl.new_lines)
@@ -1104,8 +1281,39 @@ module MarkdownExec
1104
1281
  end).to_a
1105
1282
  end
1106
1283
 
1284
+ # return code resulting from evaluating all automatic VARS blocks in order
1285
+ def code_from_auto_vars_blocks(
1286
+ all_blocks,
1287
+ block_name: @delegate_object[:document_load_vars_block_name]
1288
+ )
1289
+ # a block name is required
1290
+ # do not reload the most recent filename
1291
+ unless block_name.present? &&
1292
+ @vars_most_recent_filename != @delegate_object[:filename]
1293
+ return
1294
+ end
1295
+
1296
+ @vars_most_recent_filename = @delegate_object[:filename]
1297
+
1298
+ # select all blocks with the given name
1299
+ blocks = HashDelegator.block_select(all_blocks, :oname, block_name)
1300
+ return if blocks.empty?
1301
+
1302
+ # collect code for vars from all blocks
1303
+ (blocks.each.with_object([]) do |block, merged_options|
1304
+ merged_options.push(
1305
+ code_from_vars_block_to_set_environment_variables(block)
1306
+ )
1307
+ end).to_a
1308
+ end
1309
+
1310
+ def selected_id_name(selected)
1311
+ selected.id
1312
+ end
1313
+
1107
1314
  # parse YAML body defining the UX for a single variable
1108
1315
  # set ENV value for the variable and return code lines for the same
1316
+ # for BlockType::UX
1109
1317
  def code_from_ux_block_to_set_environment_variables(
1110
1318
  selected, mdoc, inherited_code: nil, force: true, only_default: false,
1111
1319
  silent:
@@ -1115,7 +1323,7 @@ module MarkdownExec
1115
1323
  exit_prompt = @delegate_object[:prompt_filespec_back]
1116
1324
 
1117
1325
  required = mdoc.collect_recursively_required_code(
1118
- anyname: selected.pub_name,
1326
+ anyname: selected_id_name(selected),
1119
1327
  label_format_above: @delegate_object[:shell_code_label_format_above],
1120
1328
  label_format_below: @delegate_object[:shell_code_label_format_below],
1121
1329
  block_source: block_source
@@ -1129,7 +1337,8 @@ module MarkdownExec
1129
1337
 
1130
1338
  wwt :fcb, 'a required block', block
1131
1339
 
1132
- case data = safe_yaml_load(block.body.join("\n"))
1340
+ body1 = block.raw_body || block.body
1341
+ case data = safe_yaml_load(body1.join("\n"))
1133
1342
  when Hash
1134
1343
  export = parse_yaml_of_ux_block(
1135
1344
  data,
@@ -1146,6 +1355,8 @@ module MarkdownExec
1146
1355
  export.required&.each do |precondition|
1147
1356
  required_variables.push "[[ -z $#{precondition} ]] && exit #{EXIT_STATUS_REQUIRED_EMPTY}"
1148
1357
  end
1358
+ wwt :required_variables, 'required_variables',
1359
+ required_variables
1149
1360
 
1150
1361
  eval_code = join_array_of_arrays(
1151
1362
  inherited_code, # inherited code
@@ -1153,6 +1364,7 @@ module MarkdownExec
1153
1364
  required_variables, # test conditions
1154
1365
  required[:code] # current block code
1155
1366
  )
1367
+ wwt :eval_code, 'eval_code:', eval_code
1156
1368
  if only_default
1157
1369
  command_result_w_e_t_nl =
1158
1370
  ux_block_export_automatic(eval_code, export)
@@ -1163,7 +1375,9 @@ module MarkdownExec
1163
1375
  command_result_w_e_t_nl =
1164
1376
  ux_block_export_activated(eval_code, export, exit_prompt)
1165
1377
  if command_result_w_e_t_nl.failure?
1166
- warn command_result_w_e_t_nl.warning if command_result_w_e_t_nl.warning&.present? && !silent
1378
+ if command_result_w_e_t_nl.warning&.present? && !silent
1379
+ warn command_result_w_e_t_nl.warning
1380
+ end
1167
1381
  return command_result_w_e_t_nl
1168
1382
  end
1169
1383
  end
@@ -1174,25 +1388,35 @@ module MarkdownExec
1174
1388
  process_command_result_lines(command_result_w_e_t_nl, export,
1175
1389
  required_lines)
1176
1390
  required_lines.concat(command_result_w_e_t_nl.new_lines)
1391
+
1392
+ required_lines = annotate_required_lines(
1393
+ 'blk:UX', required_lines, block_name: selected.id
1394
+ )
1395
+
1177
1396
  command_result_w_e_t_nl.new_lines = required_lines
1178
1397
  ret_command_result = command_result_w_e_t_nl
1179
1398
  else
1180
1399
  raise "Invalid data type: #{data.inspect}"
1181
1400
  end
1182
1401
  end
1402
+ wwt :required_lines, required_lines
1183
1403
 
1184
- (ret_command_result || CommandResult.new(stdout: required_lines)).tap do |ret|
1185
- wwt :cr, ret
1404
+ (ret_command_result || CommandResult.new(
1405
+ stdout: annotate_required_lines(
1406
+ 'blk:UX', required_lines, block_name: selected.id
1407
+ )
1408
+ )).tap do |ret|
1409
+ wwt :cr, ret, caller.deref
1186
1410
  end
1187
1411
  rescue StandardError
1188
1412
  wwe 'selected:', selected, 'required:', required, 'block:', block,
1189
1413
  'data:', data
1190
1414
  end
1191
1415
 
1416
+ # def
1192
1417
  # sets ENV
1193
1418
  def code_from_vars_block_to_set_environment_variables(selected)
1194
1419
  code_lines = []
1195
- # code_lines << '# code_from_vars_block_to_set_environment_variables'
1196
1420
  case data = YAML.load(selected.body.join("\n"))
1197
1421
  when Hash
1198
1422
  data.each do |key, value|
@@ -1201,12 +1425,15 @@ module MarkdownExec
1201
1425
 
1202
1426
  next unless @delegate_object[:menu_vars_set_format].present?
1203
1427
 
1428
+ # in-menu display
1204
1429
  formatted_string = format(@delegate_object[:menu_vars_set_format],
1205
1430
  { key: key, value: value })
1431
+
1432
+ # activity dump
1206
1433
  print string_send_color(formatted_string, :menu_vars_set_color)
1207
1434
  end
1208
1435
  end
1209
- code_lines
1436
+ annotate_required_lines('blk:VARS', code_lines, block_name: selected.id)
1210
1437
  rescue StandardError
1211
1438
  wwe 'selected:', selected, 'data:', data, 'key:', key, 'value:', value,
1212
1439
  'code_lines:', code_lines, 'formatted_string:', formatted_string
@@ -1386,7 +1613,7 @@ module MarkdownExec
1386
1613
 
1387
1614
  if allow_execution
1388
1615
  execute_required_lines(
1389
- blockname: selected.pub_name,
1616
+ blockname: selected_id_name(selected),
1390
1617
  erls: { play_bin: play_bin,
1391
1618
  shell: selected_shell(selected.shell) },
1392
1619
  required_lines: required_lines,
@@ -1513,7 +1740,9 @@ module MarkdownExec
1513
1740
  if wrap
1514
1741
  line_caps = line_caps.flat_map do |line_cap|
1515
1742
  text = line_cap[:text]
1516
- wrapper = StringWrapper.new(width: screen_width_for_wrapping - line_cap[:indent].length)
1743
+ wrapper = StringWrapper.new(
1744
+ width: screen_width_for_wrapping - line_cap[:indent].length
1745
+ )
1517
1746
 
1518
1747
  if text.length > screen_width_for_wrapping
1519
1748
  # Wrap this text and create line_cap objects for each part
@@ -1559,13 +1788,16 @@ module MarkdownExec
1559
1788
  decorated = apply_tree_decorations(
1560
1789
  oname, color_method, decor_patterns
1561
1790
  )
1562
-
1563
1791
  line_obj[:line] = line_obj[:indent] + line_obj[:text]
1564
1792
 
1565
1793
  if use_fcb
1566
1794
  fcb.center = center
1567
1795
  fcb.chrome = true
1568
- fcb.collapse = collapse.nil? ? (line_obj[:collapse] == COLLAPSIBLE_TOKEN_COLLAPSE) : collapse
1796
+ fcb.collapse = if collapse.nil?
1797
+ (line_obj[:collapse] == COLLAPSIBLE_TOKEN_COLLAPSE)
1798
+ else
1799
+ collapse
1800
+ end
1569
1801
  fcb.disabled = disabled ? TtyMenu::DISABLE : nil
1570
1802
  fcb.dname = line_obj[:indent] + decorated
1571
1803
  fcb.id = "#{id}.#{index}"
@@ -1583,7 +1815,12 @@ module MarkdownExec
1583
1815
  fcb = persist_fcb(
1584
1816
  center: center,
1585
1817
  chrome: true,
1586
- collapse: collapse.nil? ? (line_obj[:collapse] == COLLAPSIBLE_TOKEN_COLLAPSE) : collapse,
1818
+ collapse:
1819
+ if collapse.nil?
1820
+ (line_obj[:collapse] == COLLAPSIBLE_TOKEN_COLLAPSE)
1821
+ else
1822
+ collapse
1823
+ end,
1587
1824
  disabled: disabled ? TtyMenu::DISABLE : nil,
1588
1825
  dname: line_obj[:indent] + decorated,
1589
1826
  id: "#{id}.#{index}",
@@ -1613,53 +1850,65 @@ module MarkdownExec
1613
1850
  # @param use_chrome [Boolean] Indicates if the chrome styling should
1614
1851
  # be applied.
1615
1852
  def create_and_add_chrome_blocks(blocks, fcb, id: '', init_ids: false)
1616
- chrome_block_criteria.each_with_index do |criteria, index|
1617
- unless @delegate_object[criteria[:match]].present? &&
1618
- (mbody = fcb.body[0].match @delegate_object[criteria[:match]])
1619
- next
1853
+ index = nil
1854
+
1855
+ unless (criteria = fcb.criteria)
1856
+ HashDelegator.chrome_block_criteria.each_with_index do |criteria1, index1|
1857
+ # rubocop:disable Lint/UselessAssignment
1858
+ if !@delegate_object[criteria1[:match]].present? ||
1859
+ !(match_data = fcb.body[0].match @delegate_object[criteria1[:match]])
1860
+ next
1861
+ end
1862
+ # rubocop:enable Lint/UselessAssignment
1863
+
1864
+ criteria = criteria1
1865
+ index = index1
1866
+ break
1620
1867
  end
1868
+ end
1621
1869
 
1622
- if block_given?
1623
- # expand references only if block is recognized (not a comment)
1624
- yield if block_given?
1870
+ return unless criteria
1625
1871
 
1626
- # parse multiline to capture output of variable expansion
1627
- mbody = fcb.body[0].match Regexp.new(
1628
- @delegate_object[criteria[:match]], Regexp::MULTILINE
1629
- )
1630
- end
1872
+ if block_given?
1873
+ # expand references only if block is recognized (not a comment)
1874
+ yield if block_given?
1631
1875
 
1632
- create_and_add_chrome_block(
1633
- blocks: blocks,
1634
- case_conversion: criteria[:case_conversion],
1635
- center: criteria[:center] &&
1636
- @delegate_object[criteria[:center]],
1637
-
1638
- collapse: case fcb.collapse_token
1639
- when COLLAPSIBLE_TOKEN_COLLAPSE
1640
- true
1641
- when COLLAPSIBLE_TOKEN_EXPAND
1642
- false
1643
- else
1644
- false
1645
- end,
1646
-
1647
- color_method: criteria[:color] &&
1648
- @delegate_object[criteria[:color]].to_sym,
1649
- decor_patterns:
1650
- @decor_patterns_from_delegate_object_for_block_create,
1651
- disabled: !(criteria[:collapsible] && @delegate_object[criteria[:collapsible]]),
1652
- fcb: fcb,
1653
- id: "#{id}.#{index}",
1654
- format_option: criteria[:format] &&
1655
- @delegate_object[criteria[:format]],
1656
- level: criteria[:level],
1657
- match_data: mbody,
1658
- type: criteria[:type],
1659
- wrap: criteria[:wrap]
1876
+ # parse multiline to capture output of variable expansion
1877
+ match_data = fcb.body[0].match Regexp.new(
1878
+ @delegate_object[criteria[:match]], Regexp::MULTILINE
1660
1879
  )
1661
- break
1662
1880
  end
1881
+
1882
+ create_and_add_chrome_block(
1883
+ blocks: blocks,
1884
+ case_conversion: criteria[:case_conversion],
1885
+ center: criteria[:center] &&
1886
+ @delegate_object[criteria[:center]],
1887
+
1888
+ collapse: case fcb.collapse_token
1889
+ when COLLAPSIBLE_TOKEN_COLLAPSE
1890
+ true
1891
+ when COLLAPSIBLE_TOKEN_EXPAND
1892
+ false
1893
+ else
1894
+ false
1895
+ end,
1896
+
1897
+ color_method: criteria[:color] &&
1898
+ @delegate_object[criteria[:color]].to_sym,
1899
+ decor_patterns:
1900
+ @decor_patterns_from_delegate_object_for_block_create,
1901
+ disabled: !(criteria[:collapsible] &&
1902
+ @delegate_object[criteria[:collapsible]]),
1903
+ fcb: fcb,
1904
+ id: "#{id}.#{index}",
1905
+ format_option: criteria[:format] &&
1906
+ @delegate_object[criteria[:format]],
1907
+ level: criteria[:level],
1908
+ match_data: match_data,
1909
+ type: criteria[:type],
1910
+ wrap: criteria[:wrap]
1911
+ )
1663
1912
  end
1664
1913
 
1665
1914
  def create_divider(position, source_id: '')
@@ -1668,8 +1917,10 @@ module MarkdownExec
1668
1917
  else
1669
1918
  :menu_final_divider
1670
1919
  end
1671
- oname = format(@delegate_object[:menu_divider_format],
1672
- HashDelegator.safeval(@delegate_object[divider_key]))
1920
+ oname = format(
1921
+ @delegate_object[:menu_divider_format],
1922
+ HashDelegator.safeval(@delegate_object[divider_key])
1923
+ )
1673
1924
 
1674
1925
  persist_fcb(
1675
1926
  chrome: true,
@@ -1996,11 +2247,6 @@ module MarkdownExec
1996
2247
 
1997
2248
  LoadFileLinkState.new(LoadFile::REUSE, link_state)
1998
2249
 
1999
- # from CLI
2000
- elsif selected.nickname == @delegate_object[:menu_option_exit_name][:line]
2001
- debounce_reset
2002
- LoadFileLinkState.new(LoadFile::EXIT, link_state)
2003
-
2004
2250
  elsif @menu_user_clicked_back_link
2005
2251
  debounce_reset
2006
2252
  LoadFileLinkState.new(
@@ -2037,6 +2283,7 @@ module MarkdownExec
2037
2283
  selected,
2038
2284
  @dml_mdoc,
2039
2285
  inherited_code: @dml_link_state.inherited_lines,
2286
+ only_default: false,
2040
2287
  silent: true
2041
2288
  )
2042
2289
  ### TBD if command_result_w_e_t_nl.failure?
@@ -2154,16 +2401,19 @@ module MarkdownExec
2154
2401
  link_state: LinkState.new, block_source: {}
2155
2402
  )
2156
2403
  link_block_data = HashDelegator.parse_yaml_data_from_body(link_block_body)
2404
+
2157
2405
  ## collect blocks specified by block
2158
2406
  #
2159
2407
  if mdoc
2160
2408
  code_info = mdoc.collect_recursively_required_code(
2161
- anyname: selected.pub_name,
2409
+ anyname: selected_id_name(selected),
2410
+ block_source: block_source,
2162
2411
  label_format_above: @delegate_object[:shell_code_label_format_above],
2163
- label_format_below: @delegate_object[:shell_code_label_format_below],
2164
- block_source: block_source
2412
+ label_format_below: @delegate_object[:shell_code_label_format_below]
2413
+ )
2414
+ code_lines = annotate_required_lines(
2415
+ 'blk:LINK', code_info[:code], block_name: selected.id
2165
2416
  )
2166
- code_lines = code_info[:code]
2167
2417
  block_names = code_info[:block_names]
2168
2418
  dependencies = code_info[:dependencies]
2169
2419
  else
@@ -2198,9 +2448,8 @@ module MarkdownExec
2198
2448
 
2199
2449
  # if an eval link block, evaluate code_lines and return its standard output
2200
2450
  #
2201
- if link_block_data.fetch(LinkKeys::EVAL,
2202
- false) || link_block_data.fetch(LinkKeys::EXEC,
2203
- false)
2451
+ if link_block_data.fetch(LinkKeys::EVAL, false) ||
2452
+ link_block_data.fetch(LinkKeys::EXEC, false)
2204
2453
  code_lines += link_block_data_eval(
2205
2454
  link_state, code_lines, selected, link_block_data,
2206
2455
  block_source: block_source,
@@ -2217,6 +2466,10 @@ module MarkdownExec
2217
2466
  nil
2218
2467
  ) || link_block_data.fetch(LinkKeys::BLOCK, nil) || ''
2219
2468
 
2469
+ code_lines = annotate_required_lines(
2470
+ 'blk:LINK', code_lines, block_name: selected.id
2471
+ )
2472
+
2220
2473
  if link_block_data[LinkKeys::RETURN]
2221
2474
  pop_add_current_code_to_head_and_trigger_load(
2222
2475
  link_state, block_names, code_lines,
@@ -2229,9 +2482,9 @@ module MarkdownExec
2229
2482
  curr_block_name: selected.pub_name,
2230
2483
  curr_document_filename: @delegate_object[:filename],
2231
2484
  inherited_block_names:
2232
- ((link_state&.inherited_block_names || []) + block_names).sort.uniq,
2485
+ ((link_state&.inherited_block_names || []) + block_names).sort.uniq,
2233
2486
  inherited_dependencies:
2234
- (link_state&.inherited_dependencies || {}).merge(dependencies || {}), ### merge, not replace, key data
2487
+ (link_state&.inherited_dependencies || {}).merge(dependencies || {}), ### merge, not replace, key data
2235
2488
  inherited_lines: HashDelegator.flatten_and_compact_arrays(
2236
2489
  link_state&.inherited_lines, code_lines
2237
2490
  ),
@@ -2239,7 +2492,12 @@ module MarkdownExec
2239
2492
  next_block_name: next_block_name,
2240
2493
  next_document_filename: next_document_filename,
2241
2494
  next_keep_code: next_keep_code,
2242
- next_load_file: next_document_filename == @delegate_object[:filename] ? LoadFile::REUSE : LoadFile::LOAD
2495
+ next_load_file:
2496
+ if next_document_filename == @delegate_object[:filename]
2497
+ LoadFile::REUSE
2498
+ else
2499
+ LoadFile::LOAD
2500
+ end
2243
2501
  )
2244
2502
  end
2245
2503
  end
@@ -2279,7 +2537,9 @@ module MarkdownExec
2279
2537
  block_data['view'] || view,
2280
2538
  NamedCaptureExtractor.extract_named_group_match_data(
2281
2539
  file.match(
2282
- Regexp.new(block_data['filename_pattern'] || filename_pattern)
2540
+ Regexp.new(
2541
+ block_data['filename_pattern'] || filename_pattern
2542
+ )
2283
2543
  )
2284
2544
  )
2285
2545
  ),
@@ -2306,9 +2566,11 @@ module MarkdownExec
2306
2566
  reason = 'default Load mode'
2307
2567
  mode = LoadMode::APPEND
2308
2568
  end
2569
+ wwt :state, '@dml_block_state:', @dml_block_state
2309
2570
 
2310
2571
  OpenStruct.new(
2311
- code: code,
2572
+ code: annotate_required_lines('blk:LOAD', code,
2573
+ block_name: selected.id),
2312
2574
  mode: mode,
2313
2575
  reason: reason
2314
2576
  )
@@ -2325,7 +2587,7 @@ module MarkdownExec
2325
2587
  def execute_block_type_port_code_lines(mdoc:, selected:, block_source:,
2326
2588
  link_state: LinkState.new)
2327
2589
  required = mdoc.collect_recursively_required_code(
2328
- anyname: selected.pub_name,
2590
+ anyname: selected_id_name(selected),
2329
2591
  label_format_above: @delegate_object[:shell_code_label_format_above],
2330
2592
  label_format_below: @delegate_object[:shell_code_label_format_below],
2331
2593
  block_source: block_source
@@ -2356,15 +2618,21 @@ module MarkdownExec
2356
2618
 
2357
2619
  if selected[:type] == BlockType::OPTS
2358
2620
  # body of blocks is returned as a list of lines to be read an YAML
2359
- HashDelegator.flatten_and_compact_arrays(required[:blocks].map(&:body).flatten(1))
2621
+ HashDelegator.flatten_and_compact_arrays(
2622
+ required[:blocks].map(&:body).flatten(1)
2623
+ )
2360
2624
  else
2361
2625
  code_lines = if selected.type == BlockType::VARS
2362
2626
  code_from_vars_block_to_set_environment_variables(selected)
2363
2627
  else
2364
2628
  []
2365
2629
  end
2366
- HashDelegator.flatten_and_compact_arrays(link_state&.inherited_lines,
2367
- required[:code] + code_lines)
2630
+ HashDelegator.flatten_and_compact_arrays(
2631
+ link_state&.inherited_lines,
2632
+ annotate_required_lines(
2633
+ 'blk:PORT', required[:code] + code_lines, block_name: selected.id
2634
+ )
2635
+ )
2368
2636
  end
2369
2637
  end
2370
2638
 
@@ -2383,6 +2651,11 @@ module MarkdownExec
2383
2651
  save_filespec_from_expression(directory_glob).tap do |save_filespec|
2384
2652
  if save_filespec && save_filespec != exit_prompt
2385
2653
  begin
2654
+ if @delegate_object[:document_save_make_directory]
2655
+ # make directory if it doesn't exist
2656
+ FileUtils.mkdir_p(File.dirname(save_filespec))
2657
+ end
2658
+
2386
2659
  File.write(save_filespec,
2387
2660
  HashDelegator.join_code_lines(code_lines))
2388
2661
  rescue Errno::ENOENT
@@ -2425,8 +2698,10 @@ module MarkdownExec
2425
2698
  end
2426
2699
 
2427
2700
  # Handle stdin stream
2428
- input_thread = handle_stream(stream: $stdin,
2429
- file_type: ExecutionStreams::STD_IN) do |line|
2701
+ input_thread = handle_stream(
2702
+ stream: $stdin,
2703
+ file_type: ExecutionStreams::STD_IN
2704
+ ) do |line|
2430
2705
  stdin.puts(line)
2431
2706
  yield line, nil, nil, exec_thread if block_given?
2432
2707
  end
@@ -2555,6 +2830,7 @@ module MarkdownExec
2555
2830
  end
2556
2831
 
2557
2832
  def expand_references!(fcb, link_state)
2833
+ wwt :expand_references, 'fcb.id', fcb.id, 'link_state:', link_state
2558
2834
  # options expansions
2559
2835
  expand_variable_references!(
2560
2836
  blocks: [fcb],
@@ -2562,8 +2838,9 @@ module MarkdownExec
2562
2838
  group_name: :payload,
2563
2839
  initial_code_required: false,
2564
2840
  link_state: link_state,
2565
- pattern: @delegate_object[:option_expansion_expression_regexp].present? &&
2566
- Regexp.new(@delegate_object[:option_expansion_expression_regexp])
2841
+ pattern:
2842
+ @delegate_object[:option_expansion_expression_regexp].present? &&
2843
+ Regexp.new(@delegate_object[:option_expansion_expression_regexp])
2567
2844
  )
2568
2845
 
2569
2846
  # variable expansions
@@ -2572,7 +2849,8 @@ module MarkdownExec
2572
2849
  echo_formatter: lambda do |variable|
2573
2850
  %(echo "$#{variable}")
2574
2851
  end,
2575
- group_name: @delegate_object[:variable_expansion_name_capture_group]&.to_sym,
2852
+ group_name:
2853
+ @delegate_object[:variable_expansion_name_capture_group]&.to_sym,
2576
2854
  initial_code_required: false,
2577
2855
  link_state: link_state,
2578
2856
  pattern: options_variable_expansion_regexp
@@ -2582,12 +2860,15 @@ module MarkdownExec
2582
2860
  expand_variable_references!(
2583
2861
  blocks: [fcb],
2584
2862
  echo_formatter: lambda { |command| command },
2585
- group_name: @delegate_object[:command_substitution_name_capture_group]&.to_sym,
2863
+ group_name:
2864
+ @delegate_object[:command_substitution_name_capture_group]&.to_sym,
2586
2865
  initial_code_required: false,
2587
2866
  link_state: link_state,
2588
2867
  pattern: options_command_substitution_regexp
2589
2868
  )
2590
2869
  # no return
2870
+ rescue StandardError
2871
+ wwe 'fcb:', fcb, 'link_state:', link_state
2591
2872
  end
2592
2873
 
2593
2874
  def expand_variable_references!(
@@ -2623,55 +2904,88 @@ module MarkdownExec
2623
2904
  # no return
2624
2905
  end
2625
2906
 
2626
- def export_echo_with_code(
2627
- bash_script_lines, export, force:, silent:, string: nil
2907
+ def ux_block_eval_for_export(
2908
+ bash_script_lines, export,
2909
+ data:,
2910
+ first_only: false,
2911
+ force:,
2912
+ printf_expand: false,
2913
+ silent:,
2914
+ string: nil
2628
2915
  )
2629
- wwp
2630
2916
  exportable = true
2631
2917
  command_result = nil
2632
2918
  new_lines = []
2633
- export_string = string.nil? ? export.echo : string
2919
+ export_string = string.nil? ? data : string
2920
+ expander = ->(expression) { %(printf '%s' "#{expression}") }
2921
+
2634
2922
  case export_string
2635
2923
  when String, Integer, Float, TrueClass, FalseClass
2636
2924
  command_result, exportable, = output_from_adhoc_bash_script_file(
2637
2925
  join_array_of_arrays(
2638
2926
  bash_script_lines,
2639
- %(printf '%s' "#{export_string}")
2927
+ printf_expand ? expander.call(export_string) : [export_string]
2640
2928
  ),
2641
2929
  export,
2642
2930
  force: force
2643
2931
  )
2644
2932
  if command_result.exit_status == EXIT_STATUS_REQUIRED_EMPTY
2645
2933
  exportable = false
2646
- command_result.warning = warning_required_empty(export) unless silent
2934
+ unless silent
2935
+ command_result.warning = warning_required_empty(export)
2936
+ end
2647
2937
  else
2938
+ # store the transformed value in ENV
2648
2939
  EnvInterface.set(export.name, command_result.stdout.to_s)
2940
+
2649
2941
  new_lines << { name: export.name, force: force,
2650
2942
  text: command_result.stdout }
2651
2943
  end
2652
2944
 
2653
2945
  when Hash
2946
+ required_lines = []
2947
+
2654
2948
  # each item in the hash is a variable name and value
2655
2949
  export_string.each do |name, expression|
2656
2950
  command_result, = output_from_adhoc_bash_script_file(
2657
2951
  join_array_of_arrays(
2658
2952
  bash_script_lines,
2659
- %(printf '%s' "#{expression}")
2953
+ required_lines,
2954
+ printf_expand ? expander.call(expression) : [expression]
2660
2955
  ),
2661
2956
  export,
2662
2957
  force: force
2663
2958
  )
2664
2959
  if command_result.exit_status == EXIT_STATUS_REQUIRED_EMPTY
2665
- command_result.warning = warning_required_empty(export) unless silent
2960
+ unless silent
2961
+ command_result.warning = warning_required_empty(export)
2962
+ end
2666
2963
  else
2667
- EnvInterface.set(name, command_result.stdout.to_s)
2668
- new_lines << { name: name, force: force,
2669
- text: command_result.stdout }
2964
+ transformed = command_result.stdout.to_s
2965
+
2966
+ # code for subsequent expression evaluations
2967
+ required_lines << code_line_to_assign_a_variable(
2968
+ name, transformed, force: force
2969
+ )
2970
+
2971
+ if variable_is_exportable(name)
2972
+ # store the transformed value in ENV
2973
+ EnvInterface.set(name, transformed)
2974
+
2975
+ new_lines << { name: name, force: force,
2976
+ text: command_result.stdout }
2977
+ end
2670
2978
  end
2979
+
2980
+ break if first_only
2671
2981
  end
2982
+ else
2983
+ # do nothing
2672
2984
  end
2673
2985
 
2674
2986
  [command_result, exportable, new_lines]
2987
+ rescue StandardError
2988
+ wwe bash_script_lines, export, force, silent, string
2675
2989
  end
2676
2990
 
2677
2991
  # Retrieves a specific data symbol from the delegate object,
@@ -2711,8 +3025,13 @@ module MarkdownExec
2711
3025
  # fallback to @dml_menu_blocks if not found.
2712
3026
  def find_block_by_name(blocks, block_name)
2713
3027
  match_block = ->(item) do
2714
- [item.pub_name, item.nickname,
2715
- item.oname, item.s2title].include?(block_name)
3028
+ [
3029
+ selected_id_name(item),
3030
+ item.pub_name,
3031
+ item.nickname,
3032
+ item.oname,
3033
+ item.s2title
3034
+ ].include?(block_name)
2716
3035
  end
2717
3036
 
2718
3037
  @dml_blocks_in_file.find(&match_block) ||
@@ -2759,7 +3078,9 @@ module MarkdownExec
2759
3078
  end
2760
3079
 
2761
3080
  def format_echo_command(payload)
2762
- payload_match = payload.match(@delegate_object[:option_expansion_payload_regexp])
3081
+ payload_match = payload.match(
3082
+ @delegate_object[:option_expansion_payload_regexp]
3083
+ )
2763
3084
  variable = payload_match[:option]
2764
3085
  property = payload_match[:property]
2765
3086
 
@@ -2807,6 +3128,20 @@ module MarkdownExec
2807
3128
  string_send_color(formatted_string, color_sym)
2808
3129
  end
2809
3130
 
3131
+ # for inherited code comments
3132
+ # for external automation
3133
+ def formatted_block_name(block_name,
3134
+ format_sym = :publish_block_name_format)
3135
+ format(
3136
+ @delegate_object[format_sym],
3137
+ { block: block_name,
3138
+ document: @delegate_object[:filename],
3139
+ time: Time.now.utc.strftime(
3140
+ @delegate_object[:publish_time_format]
3141
+ ) }
3142
+ )
3143
+ end
3144
+
2810
3145
  # Expand expression if it contains format specifiers
2811
3146
  def formatted_expression(expr)
2812
3147
  expr.include?('%{') ? format_expression(expr) : expr
@@ -2994,6 +3329,7 @@ module MarkdownExec
2994
3329
  ) do |nested_line|
2995
3330
  next if nested_line.nil?
2996
3331
 
3332
+ wwt :iterlines, 'nested_line:', nested_line
2997
3333
  update_line_and_block_state(
2998
3334
  nested_line, state, selected_types,
2999
3335
  source_id: "ItrBlkFrmNstFls:#{index}¤#{nested_line.filename}:#{nested_line.index}",
@@ -3002,6 +3338,8 @@ module MarkdownExec
3002
3338
 
3003
3339
  index += 1
3004
3340
  end
3341
+ rescue StandardError
3342
+ wwe $!, 'state:', state, 'nested_line:', nested_line
3005
3343
  end
3006
3344
 
3007
3345
  def iter_source_blocks(source, source_id: nil, &block)
@@ -3036,8 +3374,9 @@ module MarkdownExec
3036
3374
 
3037
3375
  def link_block_data_eval(link_state, code_lines, selected, link_block_data,
3038
3376
  block_source:, shell:)
3039
- all_code = HashDelegator.flatten_and_compact_arrays(link_state&.inherited_lines,
3040
- code_lines)
3377
+ all_code = HashDelegator.flatten_and_compact_arrays(
3378
+ link_state&.inherited_lines, code_lines
3379
+ )
3041
3380
  output_lines = []
3042
3381
 
3043
3382
  Tempfile.open do |file|
@@ -3171,9 +3510,10 @@ module MarkdownExec
3171
3510
 
3172
3511
  # Loads and updates auto options for document blocks if the current filename has changed.
3173
3512
  #
3174
- # This method checks if the delegate object specifies a document load options block name and if the filename
3175
- # has been updated. It then selects the appropriate blocks, collects their dependencies, processes their
3176
- # options, and updates the menu base with the merged options.
3513
+ # This method checks if the delegate object specifies a document load
3514
+ # options block name and if the filename has been updated. It then
3515
+ # selects the appropriate blocks, collects their dependencies, processes
3516
+ # their options, and updates the menu base with the merged options.
3177
3517
  #
3178
3518
  # @param all_blocks [Array] An array of all block elements.
3179
3519
  # @param mdoc [Object] The document object managing dependencies and options.
@@ -3211,27 +3551,6 @@ module MarkdownExec
3211
3551
  true
3212
3552
  end
3213
3553
 
3214
- def load_auto_vars_block(
3215
- all_blocks,
3216
- block_name: @delegate_object[:document_load_vars_block_name]
3217
- )
3218
- unless block_name.present? &&
3219
- @vars_most_recent_filename != @delegate_object[:filename]
3220
- return
3221
- end
3222
-
3223
- blocks = HashDelegator.block_select(all_blocks, :oname, block_name)
3224
- return if blocks.empty?
3225
-
3226
- @vars_most_recent_filename = @delegate_object[:filename]
3227
-
3228
- (blocks.each.with_object([]) do |block, merged_options|
3229
- merged_options.push(
3230
- code_from_vars_block_to_set_environment_variables(block)
3231
- )
3232
- end).to_a
3233
- end
3234
-
3235
3554
  def load_cli_or_user_selected_block(all_blocks: [], menu_blocks: [],
3236
3555
  prior_answer: nil)
3237
3556
  if @delegate_object[:block_name].present?
@@ -3252,30 +3571,6 @@ module MarkdownExec
3252
3571
  SelectedBlockMenuState.new(block, source, state)
3253
3572
  end
3254
3573
 
3255
- def load_document_shell_block(all_blocks, mdoc: nil)
3256
- block_name = @delegate_object[:document_load_shell_block_name]
3257
- unless block_name.present? &&
3258
- @shell_most_recent_filename != @delegate_object[:filename]
3259
- return
3260
- end
3261
-
3262
- fcb = HashDelegator.block_find(all_blocks, :oname, block_name)
3263
- return unless fcb
3264
-
3265
- @shell_most_recent_filename = @delegate_object[:filename]
3266
-
3267
- if mdoc
3268
- mdoc.collect_recursively_required_code(
3269
- anyname: fcb.pub_name,
3270
- label_format_above: @delegate_object[:shell_code_label_format_above],
3271
- label_format_below: @delegate_object[:shell_code_label_format_below],
3272
- block_source: block_source
3273
- )[:code]
3274
- else
3275
- fcb.body
3276
- end
3277
- end
3278
-
3279
3574
  # format + glob + select for file in load block
3280
3575
  # name has references to ENV vars and doc and batch vars
3281
3576
  # incl. timestamp
@@ -3367,32 +3662,21 @@ module MarkdownExec
3367
3662
  reload_blocks = true
3368
3663
  end
3369
3664
 
3370
- # load document shell block
3665
+ # return code resulting from evaluating all SHELL, UX, VARS blocks;
3666
+ # each set in sequence; with its own order
3371
3667
  #
3372
- if (code_lines = load_document_shell_block(all_blocks, mdoc: mdoc))
3373
- next_state_set_code(nil, link_state, code_lines)
3374
- link_state.inherited_lines = code_lines
3375
- reload_blocks = true
3376
- end
3377
-
3378
- # load document ux block
3379
- #
3380
- if (code_lines = code_from_automatic_ux_blocks(all_blocks, mdoc))
3381
- new_code = HashDelegator.flatten_and_compact_arrays(link_state.inherited_lines,
3382
- code_lines)
3383
- next_state_set_code(nil, link_state, new_code)
3384
- link_state.inherited_lines = new_code
3385
- reload_blocks = true
3386
- end
3387
-
3388
- # load document vars block
3389
- #
3390
- if (code_lines = load_auto_vars_block(all_blocks))
3391
- new_code = HashDelegator.flatten_and_compact_arrays(link_state.inherited_lines,
3392
- code_lines)
3668
+ if (code_lines = code_from_auto_blocks(
3669
+ all_blocks,
3670
+ default_only: true,
3671
+ mdoc: mdoc
3672
+ ))&.select_by(:empty?, false)&.compact&.count&.positive?
3673
+ new_code = code_lines
3674
+ wwt :code_lines, 'code_lines:', code_lines
3393
3675
  next_state_set_code(nil, link_state, new_code)
3394
3676
  link_state.inherited_lines = new_code
3395
3677
  reload_blocks = true
3678
+ else
3679
+ link_state&.inherited_lines
3396
3680
  end
3397
3681
 
3398
3682
  if reload_blocks
@@ -3448,6 +3732,7 @@ module MarkdownExec
3448
3732
  [all_blocks, menu_blocks, mdoc]
3449
3733
  end
3450
3734
 
3735
+ # enable scroll targets in long sequences of inactive lines
3451
3736
  def handle_consecutive_inactive_items!(menu_blocks)
3452
3737
  consecutive_inactive_count = 0
3453
3738
  menu_blocks.each do |fcb|
@@ -3455,7 +3740,10 @@ module MarkdownExec
3455
3740
  consecutive_inactive_count = 0
3456
3741
  else
3457
3742
  consecutive_inactive_count += 1
3458
- if (consecutive_inactive_count % (@delegate_object[:select_page_height] / 3)).zero?
3743
+ if (consecutive_inactive_count %
3744
+ (@delegate_object[:select_page_height] /
3745
+ @delegate_object[:select_page_ratio])
3746
+ ).zero?
3459
3747
  fcb.disabled = TtyMenu::ENABLE
3460
3748
  fcb.is_enabled_but_inactive = true
3461
3749
  end
@@ -3463,7 +3751,7 @@ module MarkdownExec
3463
3751
  end
3464
3752
  end
3465
3753
 
3466
- def menu_add_disabled_option(document_glob)
3754
+ def menu_add_disabled_option(document_glob, id)
3467
3755
  raise unless document_glob.present?
3468
3756
  raise if @dml_menu_blocks.nil?
3469
3757
 
@@ -3473,13 +3761,16 @@ module MarkdownExec
3473
3761
  #
3474
3762
  return unless block.nil?
3475
3763
 
3764
+ dname = HashDelegator.new(@delegate_object).string_send_color(
3765
+ document_glob, :menu_inherited_lines_color
3766
+ )
3767
+
3476
3768
  chrome_block = persist_fcb(
3477
3769
  chrome: true,
3478
3770
  disabled: TtyMenu::DISABLE,
3479
- dname: HashDelegator.new(@delegate_object).string_send_color(
3480
- document_glob, :menu_inherited_lines_color
3481
- ),
3771
+ dname: dname,
3482
3772
  # 2025-01-03 menu item is disabled ∴ does not need a recall id
3773
+ id: id,
3483
3774
  oname: formatted_name
3484
3775
  )
3485
3776
 
@@ -3749,7 +4040,8 @@ module MarkdownExec
3749
4040
  end
3750
4041
  end
3751
4042
 
3752
- # This method handles the back-link operation in the Markdown execution context.
4043
+ # This method handles the back-link operation in
4044
+ # the Markdown execution context.
3753
4045
  # It updates the history state for the next block.
3754
4046
  #
3755
4047
  # @return [LinkState] An object indicating the state for
@@ -3804,12 +4096,13 @@ module MarkdownExec
3804
4096
  multiline = fcb.indented_decorated ||
3805
4097
  (fcb.indent + (fcb.s1decorated || fcb.dname))
3806
4098
 
3807
- fcb.name = multiline.each_line.with_index.map do |line, index|
4099
+ fcb.name = multiline.split("\n",
4100
+ -1).each_with_index.map do |line, index|
3808
4101
  if fcb.fetch(:disabled, nil).nil?
3809
4102
  index.zero? ? active : inactive
3810
4103
  else
3811
4104
  inactive
3812
- end + line.chomp
4105
+ end + line
3813
4106
  end.join("\n")
3814
4107
 
3815
4108
  fcb.value = fcb.id || fcb.name.split("\n").first
@@ -3855,8 +4148,10 @@ module MarkdownExec
3855
4148
  raise StandardError, $!
3856
4149
  end
3857
4150
 
3858
- def process_string_array(arr, begin_pattern: nil, end_pattern: nil, scan1: nil,
3859
- format1: nil, name: '')
4151
+ def process_string_array(
4152
+ arr, begin_pattern: nil, end_pattern: nil, scan1: nil,
4153
+ format1: nil, name: ''
4154
+ )
3860
4155
  in_block = !begin_pattern.present?
3861
4156
  collected_lines = []
3862
4157
 
@@ -3909,6 +4204,9 @@ module MarkdownExec
3909
4204
 
3910
4205
  @allowed_execution_block = @prior_execution_block
3911
4206
  true
4207
+ rescue TTY::Reader::InputInterrupt
4208
+ # treat as denial
4209
+ false
3912
4210
  end
3913
4211
 
3914
4212
  def prompt_for_command(prompt)
@@ -3992,6 +4290,9 @@ module MarkdownExec
3992
4290
  end
3993
4291
 
3994
4292
  sel == MenuOptions::YES
4293
+ rescue TTY::Reader::InputInterrupt
4294
+ # treat as denial
4295
+ false
3995
4296
  end
3996
4297
 
3997
4298
  def prompt_margin_left_text
@@ -4016,7 +4317,14 @@ module MarkdownExec
4016
4317
  menu.choice @delegate_object[:prompt_yes]
4017
4318
  menu.choice @delegate_object[:prompt_exit]
4018
4319
  end
4019
- sel == @delegate_object[:prompt_exit] ? MenuState::EXIT : MenuState::CONTINUE
4320
+ sel == if @delegate_object[:prompt_exit]
4321
+ MenuState::EXIT
4322
+ else
4323
+ MenuState::CONTINUE
4324
+ end
4325
+ rescue TTY::Reader::InputInterrupt
4326
+ # treat as denial
4327
+ MenuState::EXIT
4020
4328
  end
4021
4329
 
4022
4330
  # public
@@ -4045,6 +4353,9 @@ module MarkdownExec
4045
4353
  end
4046
4354
  end
4047
4355
  end
4356
+ rescue TTY::Reader::InputInterrupt
4357
+ # treat as no selection
4358
+ nil
4048
4359
  end
4049
4360
 
4050
4361
  # user prompt to exit if the menu will be displayed again
@@ -4081,15 +4392,6 @@ module MarkdownExec
4081
4392
  end
4082
4393
  end
4083
4394
 
4084
- # Handle expression with wildcard characters
4085
- # allow user to select or enter
4086
- def puts_gets_oprompt_(filespec)
4087
- puts format(@delegate_object[:prompt_show_expr_format],
4088
- { expr: filespec })
4089
- puts @delegate_object[:prompt_enter_filespec]
4090
- gets.chomp
4091
- end
4092
-
4093
4395
  def read_saved_assets_for_history_table(
4094
4396
  asset: nil,
4095
4397
  filename: nil,
@@ -4323,7 +4625,9 @@ module MarkdownExec
4323
4625
  required_lines:, selected:, shell:
4324
4626
  )
4325
4627
  write_command_file(
4326
- required_lines: required_lines, blockname: selected.pub_name, shell: selected_shell(shell)
4628
+ required_lines: required_lines,
4629
+ blockname: selected.pub_name,
4630
+ shell: selected_shell(shell)
4327
4631
  )
4328
4632
  @fout.fout "File saved: #{@run_state.saved_filespec}"
4329
4633
  end
@@ -4371,7 +4675,8 @@ module MarkdownExec
4371
4675
 
4372
4676
  def screen_width_for_table
4373
4677
  # menu adds newline after some lines if sized to the edge
4374
- screen_width - prompt_margin_left_width - prompt_margin_right_width - 3 # menu prompt symbol (1) + space (1) + gap (1)
4678
+ # menu prompt symbol (1) + space (1) + gap (1)
4679
+ screen_width - prompt_margin_left_width - prompt_margin_right_width - 3
4375
4680
  end
4376
4681
 
4377
4682
  def screen_width_for_wrapping
@@ -4472,10 +4777,14 @@ module MarkdownExec
4472
4777
  exit 1
4473
4778
  end
4474
4779
 
4475
- if selection == menu_chrome_colored_option(:menu_option_back_name)
4780
+ if selected.oname == HashDelegator.safeval(
4781
+ @delegate_object.fetch(:menu_option_back_name, '')
4782
+ )[:line]
4476
4783
  selected.option = selection
4477
4784
  selected.type = BlockType::LINK
4478
- elsif selection == menu_chrome_colored_option(:menu_option_exit_name)
4785
+ elsif selected.oname == HashDelegator.safeval(
4786
+ @delegate_object.fetch(:menu_option_exit_name, '')
4787
+ )[:line]
4479
4788
  selected.option = selection
4480
4789
  else
4481
4790
  selected.selected = selection
@@ -4616,6 +4925,7 @@ module MarkdownExec
4616
4925
  # color_sym is not found in @delegate_object.
4617
4926
  # @return [String] The string with the applied color method.
4618
4927
  def string_send_color(string, color_sym)
4928
+ ### accept string with color as well as symbol for color_hash
4619
4929
  HashDelegator.apply_color_from_hash(string, @delegate_object, color_sym)
4620
4930
  end
4621
4931
 
@@ -4705,12 +5015,23 @@ module MarkdownExec
4705
5015
  @delegate_object[:menu_include_imported_notes]
4706
5016
  # add line if it is depth 0 or option allows it
4707
5017
  #
5018
+ criteria = nil
5019
+ if @delegate_object[:table_row_single_line_match]&.present? &&
5020
+ line.match(@delegate_object[:table_row_single_line_match])
5021
+ criteria = {
5022
+ center: :table_center,
5023
+ match: :table_row_single_line_match
5024
+ }
5025
+ end
4708
5026
  HashDelegator.yield_line_if_selected(
4709
5027
  line, selected_types,
4710
5028
  all_fcbs: @fcb_store,
5029
+ criteria: criteria,
4711
5030
  source_id: source_id, &block
4712
5031
  )
4713
5032
  end
5033
+ rescue StandardError
5034
+ wwe $!, 'state:', state, 'nested_line:', nested_line
4714
5035
  end
4715
5036
 
4716
5037
  ## apply options to current state
@@ -4743,10 +5064,13 @@ module MarkdownExec
4743
5064
 
4744
5065
  case export.allow
4745
5066
  when :echo, ExportValueSource::ECHO
4746
- command_result, exportable, new_lines = export_echo_with_code(
5067
+ command_result, exportable, new_lines = ux_block_eval_for_export(
4747
5068
  bash_script_lines,
4748
5069
  export,
5070
+ data: export.echo,
5071
+ first_only: true,
4749
5072
  force: force,
5073
+ printf_expand: true,
4750
5074
  silent: silent
4751
5075
  )
4752
5076
 
@@ -4754,15 +5078,20 @@ module MarkdownExec
4754
5078
  command_result
4755
5079
  else
4756
5080
  command_result = CommandResult.new(
4757
- stdout: menu_from_list_with_back(command_result.stdout.split("\n"))
5081
+ stdout: menu_from_list_with_back(
5082
+ command_result.stdout.split("\n")
5083
+ )
4758
5084
  )
4759
5085
  end
4760
5086
 
4761
5087
  when :exec, UxActSource::EXEC
4762
- command_result, = output_from_adhoc_bash_script_file(
4763
- join_array_of_arrays(bash_script_lines, export.exec),
5088
+ command_result, exportable, new_lines = ux_block_eval_for_export(
5089
+ bash_script_lines,
4764
5090
  export,
4765
- force: force
5091
+ data: export.exec,
5092
+ first_only: true,
5093
+ force: force,
5094
+ silent: silent
4766
5095
  )
4767
5096
 
4768
5097
  if command_result.exit_status == EXIT_STATUS_REQUIRED_EMPTY
@@ -4777,10 +5106,13 @@ module MarkdownExec
4777
5106
 
4778
5107
  else
4779
5108
  export_init = menu_from_list_with_back(export.allow)
4780
- command_result, exportable, new_lines = export_echo_with_code(
5109
+ command_result, exportable, new_lines = ux_block_eval_for_export(
4781
5110
  [assign_key_value_in_bash(export.name, export_init)],
4782
5111
  export,
5112
+ data: export.echo,
5113
+ first_only: true,
4783
5114
  force: force,
5115
+ printf_expand: true,
4784
5116
  silent: silent,
4785
5117
  string: export_init
4786
5118
  )
@@ -4788,11 +5120,12 @@ module MarkdownExec
4788
5120
  end
4789
5121
 
4790
5122
  when :echo, UxActSource::ECHO
4791
- wwp
4792
- command_result, exportable, new_lines = export_echo_with_code(
5123
+ command_result, exportable, new_lines = ux_block_eval_for_export(
4793
5124
  bash_script_lines,
4794
5125
  export,
5126
+ data: export.echo,
4795
5127
  force: force,
5128
+ printf_expand: true,
4796
5129
  silent: silent
4797
5130
  )
4798
5131
 
@@ -4822,10 +5155,12 @@ module MarkdownExec
4822
5155
  command_result = CommandResult.new(stdout: output)
4823
5156
 
4824
5157
  when :exec, UxActSource::EXEC
4825
- command_result, exportable, new_lines = output_from_adhoc_bash_script_file(
4826
- join_array_of_arrays(bash_script_lines, export.exec),
5158
+ command_result, exportable, new_lines = ux_block_eval_for_export(
5159
+ bash_script_lines,
4827
5160
  export,
4828
- force: force
5161
+ data: export.exec,
5162
+ force: force,
5163
+ silent: silent
4829
5164
  )
4830
5165
 
4831
5166
  else
@@ -4843,6 +5178,8 @@ module MarkdownExec
4843
5178
  command_result.transformable = transformable
4844
5179
  command_result.new_lines = new_lines
4845
5180
  command_result
5181
+ rescue StandardError
5182
+ wwe bash_script_lines, export, exit_prompt
4846
5183
  end
4847
5184
 
4848
5185
  def ux_block_export_automatic(
@@ -4866,36 +5203,47 @@ module MarkdownExec
4866
5203
 
4867
5204
  case export.allow
4868
5205
  when :echo, ExportValueSource::ECHO
4869
- cr_echo, = output_from_adhoc_bash_script_file(
4870
- join_array_of_arrays(
4871
- bash_script_lines,
4872
- %(printf '%s' "#{export.echo}")
4873
- ),
5206
+ cr_echo, = ux_block_eval_for_export(
5207
+ bash_script_lines,
4874
5208
  export,
4875
- force: force
5209
+ data: export.echo,
5210
+ first_only: true,
5211
+ force: force,
5212
+ printf_expand: true,
5213
+ silent: silent
4876
5214
  )
4877
5215
  export_init = cr_echo.stdout.split("\n").first
4878
- command_result, exportable, new_lines = export_echo_with_code(
5216
+
5217
+ command_result, exportable, new_lines = ux_block_eval_for_export(
4879
5218
  [assign_key_value_in_bash(export.name, export_init)],
4880
5219
  export,
5220
+ data: export.echo,
5221
+ first_only: true,
4881
5222
  force: force,
5223
+ printf_expand: true,
4882
5224
  silent: silent,
4883
5225
  string: export_init
4884
5226
  )
4885
5227
 
4886
5228
  when :exec, ExportValueSource::EXEC
4887
5229
  # extract first line from 'exec' output
4888
- command_result, exportable, new_lines = output_from_adhoc_bash_script_file(
4889
- join_array_of_arrays(bash_script_lines, export.exec),
5230
+ command_result, exportable, new_lines = ux_block_eval_for_export(
5231
+ bash_script_lines,
4890
5232
  export,
4891
- force: force
5233
+ data: export.exec,
5234
+ first_only: true,
5235
+ force: force,
5236
+ silent: silent
4892
5237
  )
5238
+
4893
5239
  unless command_result.failure?
4894
5240
  export_init = command_result.stdout.split("\n").first
4895
- command_result, exportable, new_lines = export_echo_with_code(
5241
+ command_result, exportable, new_lines = ux_block_eval_for_export(
4896
5242
  [assign_key_value_in_bash(export.name, export_init)],
4897
5243
  export,
5244
+ data: export.exec,
4898
5245
  force: force,
5246
+ printf_expand: true,
4899
5247
  silent: silent,
4900
5248
  string: export_init
4901
5249
  )
@@ -4904,10 +5252,12 @@ module MarkdownExec
4904
5252
  else
4905
5253
  # first item from 'allow' list
4906
5254
  export_init = export.allow.first
4907
- command_result, exportable, new_lines = export_echo_with_code(
5255
+ command_result, exportable, new_lines = ux_block_eval_for_export(
4908
5256
  [assign_key_value_in_bash(export.name, export_init)],
4909
5257
  export,
5258
+ data: export.allow,
4910
5259
  force: force,
5260
+ printf_expand: true,
4911
5261
  silent: silent,
4912
5262
  string: export_init
4913
5263
  )
@@ -4921,32 +5271,37 @@ module MarkdownExec
4921
5271
  when :echo, UxActSource::ECHO
4922
5272
  raise unless export.echo.present?
4923
5273
 
4924
- command_result, exportable, new_lines = export_echo_with_code(
5274
+ command_result, exportable, new_lines = ux_block_eval_for_export(
4925
5275
  bash_script_lines,
4926
5276
  export,
5277
+ data: export.echo,
4927
5278
  force: force,
5279
+ printf_expand: true,
4928
5280
  silent: silent
4929
5281
  )
4930
5282
 
4931
5283
  when :exec, UxActSource::EXEC
4932
5284
  raise unless export.exec.present?
4933
5285
 
4934
- command_result, exportable, new_lines = output_from_adhoc_bash_script_file(
4935
- join_array_of_arrays(bash_script_lines, export.exec),
5286
+ command_result, exportable, new_lines = ux_block_eval_for_export(
5287
+ bash_script_lines,
4936
5288
  export,
4937
- force: force
5289
+ data: export.exec,
5290
+ force: force,
5291
+ silent: silent
4938
5292
  )
4939
5293
 
4940
5294
  else
4941
5295
  export_init = export.init.to_s
4942
- command_result, exportable, new_lines = export_echo_with_code(
5296
+ command_result, exportable, new_lines = ux_block_eval_for_export(
4943
5297
  [assign_key_value_in_bash(export.name, export_init)],
4944
5298
  export,
5299
+ data: export.exec,
4945
5300
  force: force,
5301
+ printf_expand: true,
4946
5302
  silent: silent,
4947
5303
  string: export_init
4948
5304
  )
4949
- # raise "Unknown FCB.init_source(export) #{FCB.init_source(export)}"
4950
5305
  end
4951
5306
 
4952
5307
  # add message for required variables
@@ -4959,10 +5314,19 @@ module MarkdownExec
4959
5314
  command_result.transformable = transformable
4960
5315
  command_result.new_lines = new_lines
4961
5316
  command_result
5317
+ rescue StandardError
5318
+ wwe bash_script_lines, export
4962
5319
  end
4963
5320
 
4964
- def warning_required_empty(export)
4965
- "A value must exist for: #{export.required.join(', ')}"
5321
+ # true if the variable is exported in a series of evaluations
5322
+ def variable_is_exportable(name)
5323
+ local_name_pattern = @delegate_object.fetch(:local_name_pattern, '')
5324
+
5325
+ # export all if rule is empty
5326
+ return true if local_name_pattern.empty?
5327
+
5328
+ # export if it its name does not match the rule
5329
+ name !~ Regexp.new(local_name_pattern)
4966
5330
  end
4967
5331
 
4968
5332
  def vux_await_user_selection(prior_answer: @dml_block_selection)
@@ -4981,17 +5345,119 @@ module MarkdownExec
4981
5345
  end
4982
5346
 
4983
5347
  def vux_clear_menu_state
5348
+ @dml_block_selection = @dml_block_state.block
4984
5349
  @dml_block_state = SelectedBlockMenuState.new
4985
5350
  @delegate_object[:block_name] = nil
4986
5351
  end
4987
5352
 
4988
5353
  def vux_edit_inherited
4989
5354
  edited = edit_text(@dml_link_state.inherited_lines_block)
4990
- @dml_link_state.inherited_lines = edited.split("\n") if edited
5355
+ return unless edited
5356
+
5357
+ @dml_link_state.inherited_lines =
5358
+ annotate_required_lines(
5359
+ 'blk:EDIT', edited.split("\n"), block_name: 'EDIT'
5360
+ )
5361
+ end
5362
+
5363
+ def block_is_back(fcb)
5364
+ fcb.oname == HashDelegator.safeval(
5365
+ @delegate_object[:menu_option_back_name]
5366
+ )[:line]
5367
+ end
5368
+
5369
+ def block_is_edit(fcb)
5370
+ fcb.oname == HashDelegator.safeval(
5371
+ @delegate_object[:menu_option_edit_name]
5372
+ )[:line]
5373
+ end
5374
+
5375
+ def block_is_exit(fcb)
5376
+ fcb.oname == HashDelegator.safeval(
5377
+ @delegate_object[:menu_option_exit_name]
5378
+ )[:line]
5379
+ end
5380
+
5381
+ def block_is_history(fcb)
5382
+ fcb.oname == HashDelegator.safeval(
5383
+ @delegate_object[:menu_option_history_name]
5384
+ )[:line]
5385
+ end
5386
+
5387
+ def block_is_load(fcb)
5388
+ fcb.oname == HashDelegator.safeval(
5389
+ @delegate_object[:menu_option_load_name]
5390
+ )[:line]
5391
+ end
5392
+
5393
+ def block_is_save(fcb)
5394
+ fcb.oname == HashDelegator.safeval(
5395
+ @delegate_object[:menu_option_save_name]
5396
+ )[:line]
5397
+ end
5398
+
5399
+ def block_is_shell(fcb)
5400
+ fcb.oname == HashDelegator.safeval(
5401
+ @delegate_object[:menu_option_shell_name]
5402
+ )[:line]
5403
+ end
5404
+
5405
+ def block_is_view(fcb)
5406
+ fcb.oname == HashDelegator.safeval(
5407
+ @delegate_object[:menu_option_view_name]
5408
+ )[:line]
4991
5409
  end
4992
5410
 
4993
5411
  def vux_execute_and_prompt(block_name)
4994
5412
  @dml_block_state = find_block_state_by_name(block_name)
5413
+
5414
+ if @dml_block_state.block
5415
+ if block_is_back(@dml_block_state.block)
5416
+ debounce_reset
5417
+ vux_navigate_back_for_ls
5418
+ return
5419
+
5420
+ elsif block_is_edit(@dml_block_state.block)
5421
+ debounce_reset
5422
+ vux_edit_inherited
5423
+ return pause_user_exit ? :break : nil
5424
+
5425
+ elsif block_is_exit(@dml_block_state.block)
5426
+ debounce_reset
5427
+ ### LoadFileLinkState.new(LoadFile::EXIT, link_state)
5428
+ return :break
5429
+
5430
+ elsif block_is_history(@dml_block_state.block)
5431
+ debounce_reset
5432
+ return :break unless (files_table_rows = vux_history_files_table_rows)
5433
+
5434
+ execute_history_select(files_table_rows, stream: $stderr)
5435
+ return :break if pause_user_exit
5436
+
5437
+ return
5438
+
5439
+ elsif block_is_load(@dml_block_state.block)
5440
+ debounce_reset
5441
+ vux_load_inherited
5442
+ return pause_user_exit ? :break : nil
5443
+
5444
+ elsif block_is_save(@dml_block_state.block)
5445
+ debounce_reset
5446
+ return execute_inherited_save == :break ? :break : nil
5447
+
5448
+ elsif block_is_shell(@dml_block_state.block)
5449
+ debounce_reset
5450
+ vux_input_and_execute_shell_commands(stream: $stderr, shell: shell)
5451
+ return pause_user_exit ? :break : nil
5452
+
5453
+ elsif block_is_view(@dml_block_state.block)
5454
+ debounce_reset
5455
+ vux_view_inherited(stream: $stderr)
5456
+ return pause_user_exit ? :break : nil
5457
+
5458
+ end
5459
+ end
5460
+
4995
5461
  if @dml_block_state.block &&
4996
5462
  @dml_block_state.block.type == BlockType::OPTS
4997
5463
  debounce_reset
@@ -5032,62 +5498,12 @@ module MarkdownExec
5032
5498
  end
5033
5499
 
5034
5500
  def vux_execute_block_per_type(block_name, formatted_choice_ostructs)
5035
- case block_name
5036
- when formatted_choice_ostructs[:back].pub_name
5037
- debounce_reset
5038
- vux_navigate_back_for_ls
5501
+ return :break if vux_execute_and_prompt(block_name) == :break
5039
5502
 
5040
- when formatted_choice_ostructs[:edit].pub_name
5041
- debounce_reset
5042
- vux_edit_inherited
5043
- return :break if pause_user_exit
5044
-
5045
- InputSequencer.next_link_state(prior_block_was_link: true)
5046
-
5047
- when formatted_choice_ostructs[:history].pub_name
5048
- debounce_reset
5049
- return :break unless (files_table_rows = vux_history_files_table_rows)
5050
-
5051
- execute_history_select(files_table_rows, stream: $stderr)
5052
- return :break if pause_user_exit
5053
-
5054
- InputSequencer.next_link_state(prior_block_was_link: true)
5055
-
5056
- when formatted_choice_ostructs[:load].pub_name
5057
- debounce_reset
5058
- vux_load_inherited
5059
- return :break if pause_user_exit
5060
-
5061
- InputSequencer.next_link_state(prior_block_was_link: true)
5062
-
5063
- when formatted_choice_ostructs[:save].pub_name
5064
- debounce_reset
5065
- return :break if execute_inherited_save == :break
5066
-
5067
- InputSequencer.next_link_state(prior_block_was_link: true)
5068
-
5069
- when formatted_choice_ostructs[:shell].pub_name
5070
- debounce_reset
5071
- vux_input_and_execute_shell_commands(stream: $stderr, shell: shell)
5072
- return :break if pause_user_exit
5073
-
5074
- InputSequencer.next_link_state(prior_block_was_link: true)
5075
-
5076
- when formatted_choice_ostructs[:view].pub_name
5077
- debounce_reset
5078
- vux_view_inherited(stream: $stderr)
5079
- return :break if pause_user_exit
5080
-
5081
- InputSequencer.next_link_state(prior_block_was_link: true)
5082
-
5083
- else
5084
- return :break if vux_execute_and_prompt(block_name) == :break
5085
-
5086
- InputSequencer.next_link_state(
5087
- block_name: @dml_link_state.block_name,
5088
- prior_block_was_link: @dml_block_state.block.type != BlockType::SHELL
5089
- )
5090
- end
5503
+ InputSequencer.next_link_state(
5504
+ block_name: @dml_link_state.block_name,
5505
+ prior_block_was_link: @dml_block_state.block.type != BlockType::SHELL
5506
+ )
5091
5507
  end
5092
5508
 
5093
5509
  def vux_formatted_names_for_state_chrome_blocks(
@@ -5237,10 +5653,13 @@ module MarkdownExec
5237
5653
  block_list
5238
5654
  ).run do |msg, data|
5239
5655
  count += 1
5656
+ wwt :id, 'count:', count, 'msg:', msg, 'data:', data
5240
5657
  case msg
5241
5658
  when :parse_document # once for each menu
5242
5659
  count = 0
5243
- vux_parse_document(source_id: "#{@delegate_object[:filename]}¤VuxMainLoop®PrsDoc")
5660
+ vux_parse_document(
5661
+ source_id: "#{@delegate_object[:filename]}¤VuxMainLoop®PrsDoc"
5662
+ )
5244
5663
  vux_menu_append_history_files(
5245
5664
  formatted_choice_ostructs,
5246
5665
  source_id: "#{@delegate_object[:filename]}¤VuxMainLoop®HstFls"
@@ -5254,8 +5673,19 @@ module MarkdownExec
5254
5673
  when :end_of_cli
5255
5674
  # yield :end_of_cli, @delegate_object
5256
5675
 
5257
- if @delegate_object[:list_blocks]
5258
- list_blocks(source_id: "#{@delegate_object[:filename]}¤VuxMainLoop®EndCLI")
5676
+ if @delegate_object[:blocks].present?
5677
+ @delegate_object[:list_blocks_message] =
5678
+ @delegate_object[:blocks].to_sym
5679
+ @delegate_object[:list_blocks_type] = 3
5680
+
5681
+ list_blocks(
5682
+ source_id: "#{@delegate_object[:filename]}¤VuxMainLoop®EndCLI"
5683
+ )
5684
+ :exit
5685
+ elsif @delegate_object[:list_blocks]
5686
+ list_blocks(
5687
+ source_id: "#{@delegate_object[:filename]}¤VuxMainLoop®EndCLI"
5688
+ )
5259
5689
  :exit
5260
5690
  end
5261
5691
 
@@ -5263,9 +5693,14 @@ module MarkdownExec
5263
5693
  vux_user_selected_block_name
5264
5694
 
5265
5695
  when :execute_block
5266
- ret = vux_execute_block_per_type(data, formatted_choice_ostructs)
5267
- vux_publish_block_name_for_external_automation(data)
5268
- ret
5696
+ begin
5697
+ ret = vux_execute_block_per_type(data, formatted_choice_ostructs)
5698
+ vux_publish_block_name_for_external_automation(data)
5699
+ ret
5700
+ rescue StandardError
5701
+ # error executing block, do not abort
5702
+ InputSequencer.next_link_state(prior_block_was_link: false)
5703
+ end
5269
5704
 
5270
5705
  when :close_ux
5271
5706
  if @vux_pipe_open.present? && File.exist?(@vux_pipe_open)
@@ -5321,7 +5756,7 @@ module MarkdownExec
5321
5756
 
5322
5757
  # add menu items (glob, load, save) and enable selectively
5323
5758
  if files.count.positive? || lines_count.positive?
5324
- menu_add_disabled_option(document_glob)
5759
+ menu_add_disabled_option(document_glob, "#{source_id}_vmahf_glob")
5325
5760
  end
5326
5761
  if files.count.positive?
5327
5762
  dml_menu_append_chrome_item(
@@ -5402,14 +5837,7 @@ module MarkdownExec
5402
5837
 
5403
5838
  def vux_publish_block_name_for_external_automation(block_name)
5404
5839
  publish_for_external_automation(
5405
- message: format(
5406
- @delegate_object[:publish_block_name_format],
5407
- { block: block_name,
5408
- document: @delegate_object[:filename],
5409
- time: Time.now.utc.strftime(
5410
- @delegate_object[:publish_time_format]
5411
- ) }
5412
- )
5840
+ message: formatted_block_name(block_name)
5413
5841
  )
5414
5842
  end
5415
5843
 
@@ -5445,7 +5873,7 @@ module MarkdownExec
5445
5873
  return :break if @dml_block_state.block.nil? # no block matched
5446
5874
  end
5447
5875
  # puts "! - Executing block: #{data}"
5448
- @dml_block_state.block&.pub_name
5876
+ @dml_block_state.block&.id
5449
5877
  end
5450
5878
 
5451
5879
  def vux_view_inherited(stream:)
@@ -5488,11 +5916,12 @@ module MarkdownExec
5488
5916
  nil
5489
5917
  when String
5490
5918
  menu_blocks.find do |block|
5491
- block.dname.include?(prior_answer)
5919
+ block.id == prior_answer
5492
5920
  end&.name
5493
5921
  when Struct, MarkdownExec::FCB
5494
5922
  if prior_answer.id
5495
- # when switching documents, the prior answer will not be found
5923
+ # when switching documents,
5924
+ # the prior answer will not be found
5496
5925
  (menu_blocks.find_index do |block|
5497
5926
  block[:id] == prior_answer.id
5498
5927
  end || 0) + 1
@@ -5519,6 +5948,10 @@ module MarkdownExec
5519
5948
  determine_block_state(selected_option)
5520
5949
  end
5521
5950
 
5951
+ def warning_required_empty(export)
5952
+ "A value must exist for: #{export.required.join(', ')}"
5953
+ end
5954
+
5522
5955
  # Handles the core logic for generating the command
5523
5956
  # file's metadata and content.
5524
5957
  def write_command_file(required_lines:, blockname:, shell: nil)
@@ -5590,8 +6023,10 @@ module MarkdownExec
5590
6023
  if save_expr.present?
5591
6024
  save_filespec = save_filespec_from_expression(save_expr)
5592
6025
  if save_filespec.present?
5593
- File.write(save_filespec,
5594
- HashDelegator.join_code_lines(link_state&.inherited_lines))
6026
+ File.write(
6027
+ save_filespec,
6028
+ HashDelegator.join_code_lines(link_state&.inherited_lines)
6029
+ )
5595
6030
  @delegate_object[:filename]
5596
6031
  else
5597
6032
  link_block_data[LinkKeys::FILE] || @delegate_object[:filename]
@@ -5602,6 +6037,7 @@ module MarkdownExec
5602
6037
  end
5603
6038
 
5604
6039
  def safe_yaml_load(body)
6040
+ caller.deref(12)
5605
6041
  return {} unless body&.present?
5606
6042
 
5607
6043
  YAML.load(body.to_s)
@@ -6725,4 +7161,427 @@ module MarkdownExec
6725
7161
  end
6726
7162
  end
6727
7163
  end
7164
+
7165
+ # Comprehensive tests for ux_block_eval_for_export function
7166
+ class TestUxBlockEvalForExport < Minitest::Test
7167
+ def setup
7168
+ @hd = HashDelegator.new
7169
+ @bash_script_lines = ['#!/bin/bash', 'set -e']
7170
+ @export = OpenStruct.new(name: 'TEST_VAR', required: ['TEST_VAR'])
7171
+ @mock_result = OpenStruct.new(
7172
+ exit_status: 0,
7173
+ stdout: 'test_output',
7174
+ stderr: '',
7175
+ warning: nil
7176
+ )
7177
+ end
7178
+
7179
+ def teardown
7180
+ # Clean up environment variables set during tests
7181
+ ENV.delete('TEST_VAR')
7182
+ ENV.delete('VAR1')
7183
+ ENV.delete('VAR2')
7184
+ end
7185
+
7186
+ # Test string input - typical case
7187
+ def test_string_input_success
7188
+ @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7189
+ true])
7190
+ @hd.stubs(:variable_is_exportable).returns(true)
7191
+
7192
+ result = @hd.ux_block_eval_for_export(
7193
+ @bash_script_lines, @export,
7194
+ data: 'echo "hello"',
7195
+ force: false,
7196
+ silent: false
7197
+ )
7198
+
7199
+ command_result, exportable, new_lines = result
7200
+ assert_equal @mock_result, command_result
7201
+ assert_equal true, exportable
7202
+ assert_equal 1, new_lines.length
7203
+ assert_equal 'TEST_VAR', new_lines.first[:name]
7204
+ assert_equal 'test_output', new_lines.first[:text]
7205
+ end
7206
+
7207
+ # Test string input with printf_expand
7208
+ def test_string_input_with_printf_expand
7209
+ @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7210
+ true])
7211
+ @hd.stubs(:variable_is_exportable).returns(true)
7212
+
7213
+ @hd.ux_block_eval_for_export(
7214
+ @bash_script_lines, @export,
7215
+ data: 'test_value',
7216
+ force: false,
7217
+ printf_expand: true,
7218
+ silent: false
7219
+ )
7220
+
7221
+ # Verify that join_array_of_arrays was called with printf wrapped expression
7222
+ # This tests that the expander lambda is applied correctly
7223
+ end
7224
+
7225
+ # Test integer input
7226
+ def test_integer_input
7227
+ @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7228
+ true])
7229
+ @hd.stubs(:variable_is_exportable).returns(true)
7230
+
7231
+ result = @hd.ux_block_eval_for_export(
7232
+ @bash_script_lines, @export,
7233
+ data: 42,
7234
+ force: true,
7235
+ silent: false
7236
+ )
7237
+
7238
+ command_result, exportable, new_lines = result
7239
+ assert_equal @mock_result, command_result
7240
+ assert_equal true, exportable
7241
+ assert_equal 1, new_lines.length
7242
+ end
7243
+
7244
+ # Test boolean inputs
7245
+ def test_boolean_true_input
7246
+ @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7247
+ true])
7248
+ @hd.stubs(:variable_is_exportable).returns(true)
7249
+
7250
+ result = @hd.ux_block_eval_for_export(
7251
+ @bash_script_lines, @export,
7252
+ data: true,
7253
+ force: false,
7254
+ silent: false
7255
+ )
7256
+
7257
+ command_result, exportable, = result
7258
+ assert_equal @mock_result, command_result
7259
+ assert_equal true, exportable
7260
+ end
7261
+
7262
+ def test_boolean_false_input
7263
+ @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7264
+ true])
7265
+ @hd.stubs(:variable_is_exportable).returns(true)
7266
+
7267
+ result = @hd.ux_block_eval_for_export(
7268
+ @bash_script_lines, @export,
7269
+ data: false,
7270
+ force: false,
7271
+ silent: false
7272
+ )
7273
+
7274
+ command_result, exportable, = result
7275
+ assert_equal @mock_result, command_result
7276
+ assert_equal true, exportable
7277
+ end
7278
+
7279
+ # Test float input
7280
+ def test_float_input
7281
+ @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7282
+ true])
7283
+ @hd.stubs(:variable_is_exportable).returns(true)
7284
+
7285
+ result = @hd.ux_block_eval_for_export(
7286
+ @bash_script_lines, @export,
7287
+ data: 3.14,
7288
+ force: false,
7289
+ silent: false
7290
+ )
7291
+
7292
+ command_result, exportable, = result
7293
+ assert_equal @mock_result, command_result
7294
+ assert_equal true, exportable
7295
+ end
7296
+
7297
+ # Test hash input - typical case
7298
+ def test_hash_input_success
7299
+ hash_data = { 'VAR1' => 'value1', 'VAR2' => 'value2' }
7300
+ @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7301
+ true])
7302
+ @hd.stubs(:variable_is_exportable).returns(true)
7303
+ @hd.stubs(:code_line_to_assign_a_variable).returns('VAR1=value1')
7304
+
7305
+ result = @hd.ux_block_eval_for_export(
7306
+ @bash_script_lines, @export,
7307
+ data: hash_data,
7308
+ force: false,
7309
+ silent: false
7310
+ )
7311
+
7312
+ command_result, exportable, new_lines = result
7313
+ assert_equal @mock_result, command_result
7314
+ assert_equal true, exportable
7315
+ assert_equal 2, new_lines.length
7316
+ assert_equal 'VAR1', new_lines.first[:name]
7317
+ assert_equal 'VAR2', new_lines.last[:name]
7318
+ end
7319
+
7320
+ # Test hash input with first_only flag
7321
+ def test_hash_input_first_only
7322
+ hash_data = { 'VAR1' => 'value1', 'VAR2' => 'value2' }
7323
+ @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7324
+ true])
7325
+ @hd.stubs(:variable_is_exportable).returns(true)
7326
+ @hd.stubs(:code_line_to_assign_a_variable).returns('VAR1=value1')
7327
+
7328
+ result = @hd.ux_block_eval_for_export(
7329
+ @bash_script_lines, @export,
7330
+ data: hash_data,
7331
+ first_only: true,
7332
+ force: false,
7333
+ silent: false
7334
+ )
7335
+
7336
+ command_result, _, new_lines = result
7337
+ assert_equal @mock_result, command_result
7338
+ assert_equal 1, new_lines.length
7339
+ assert_equal 'VAR1', new_lines.first[:name]
7340
+ end
7341
+
7342
+ # Test hash with non-exportable variables
7343
+ def test_hash_input_non_exportable_variables
7344
+ hash_data = { 'LOCAL_VAR' => 'value1' }
7345
+ @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7346
+ true])
7347
+ @hd.stubs(:variable_is_exportable).returns(false)
7348
+ @hd.stubs(:code_line_to_assign_a_variable).returns('LOCAL_VAR=value1')
7349
+
7350
+ result = @hd.ux_block_eval_for_export(
7351
+ @bash_script_lines, @export,
7352
+ data: hash_data,
7353
+ force: false,
7354
+ silent: false
7355
+ )
7356
+
7357
+ command_result, _, new_lines = result
7358
+ assert_equal @mock_result, command_result
7359
+ assert_equal 0, new_lines.length # No exportable variables
7360
+ end
7361
+
7362
+ # Test EXIT_STATUS_REQUIRED_EMPTY handling
7363
+ def test_exit_status_required_empty_with_warning
7364
+ failed_result = OpenStruct.new(
7365
+ exit_status: 248, # EXIT_STATUS_REQUIRED_EMPTY
7366
+ stdout: '',
7367
+ stderr: 'Required variable empty',
7368
+ warning: nil
7369
+ )
7370
+ @hd.stubs(:output_from_adhoc_bash_script_file).returns([failed_result,
7371
+ false])
7372
+ @hd.stubs(:warning_required_empty).returns('Warning: required empty')
7373
+
7374
+ result = @hd.ux_block_eval_for_export(
7375
+ @bash_script_lines, @export,
7376
+ data: 'echo ""',
7377
+ force: false,
7378
+ silent: false
7379
+ )
7380
+
7381
+ command_result, exportable, new_lines = result
7382
+ assert_equal failed_result, command_result
7383
+ assert_equal false, exportable
7384
+ assert_equal 0, new_lines.length
7385
+ assert_equal 'Warning: required empty', command_result.warning
7386
+ end
7387
+
7388
+ # Test EXIT_STATUS_REQUIRED_EMPTY handling with silent flag
7389
+ def test_exit_status_required_empty_silent
7390
+ failed_result = OpenStruct.new(
7391
+ exit_status: 248,
7392
+ stdout: '',
7393
+ stderr: 'Required variable empty',
7394
+ warning: nil
7395
+ )
7396
+ @hd.stubs(:output_from_adhoc_bash_script_file).returns([failed_result,
7397
+ false])
7398
+
7399
+ result = @hd.ux_block_eval_for_export(
7400
+ @bash_script_lines, @export,
7401
+ data: 'echo ""',
7402
+ force: false,
7403
+ silent: true
7404
+ )
7405
+
7406
+ command_result, exportable, = result
7407
+ assert_equal failed_result, command_result
7408
+ assert_equal false, exportable
7409
+ assert_nil command_result.warning # No warning when silent
7410
+ end
7411
+
7412
+ # Test string parameter override
7413
+ def test_string_parameter_override
7414
+ @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7415
+ true])
7416
+ @hd.stubs(:variable_is_exportable).returns(true)
7417
+
7418
+ result = @hd.ux_block_eval_for_export(
7419
+ @bash_script_lines, @export,
7420
+ data: 'original_data',
7421
+ string: 'override_string',
7422
+ force: false,
7423
+ silent: false
7424
+ )
7425
+
7426
+ # The function should use string parameter instead of data
7427
+ command_result, exportable, = result
7428
+ assert_equal @mock_result, command_result
7429
+ assert_equal true, exportable
7430
+ end
7431
+
7432
+ # Edge case: empty string
7433
+ def test_empty_string_input
7434
+ @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7435
+ true])
7436
+ @hd.stubs(:variable_is_exportable).returns(true)
7437
+
7438
+ result = @hd.ux_block_eval_for_export(
7439
+ @bash_script_lines, @export,
7440
+ data: '',
7441
+ force: false,
7442
+ silent: false
7443
+ )
7444
+
7445
+ command_result, exportable, = result
7446
+ assert_equal @mock_result, command_result
7447
+ assert_equal true, exportable
7448
+ end
7449
+
7450
+ # Edge case: nil data (uses string.nil? check)
7451
+ def test_nil_data_input
7452
+ @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7453
+ true])
7454
+ @hd.stubs(:variable_is_exportable).returns(true)
7455
+
7456
+ result = @hd.ux_block_eval_for_export(
7457
+ @bash_script_lines, @export,
7458
+ data: 'd1',
7459
+ force: false,
7460
+ silent: false
7461
+ )
7462
+
7463
+ command_result, exportable, = result
7464
+ assert_equal @mock_result, command_result
7465
+ assert_equal true, exportable
7466
+ end
7467
+
7468
+ # Edge case: empty hash
7469
+ def test_empty_hash_input
7470
+ result = @hd.ux_block_eval_for_export(
7471
+ @bash_script_lines, @export,
7472
+ data: {},
7473
+ force: false,
7474
+ silent: false
7475
+ )
7476
+
7477
+ command_result, exportable, new_lines = result
7478
+ assert_nil command_result # No iterations, so command_result remains nil
7479
+ assert_equal true, exportable # Initial value
7480
+ assert_equal 0, new_lines.length
7481
+ end
7482
+
7483
+ # Edge case: hash with nil values
7484
+ def test_hash_with_nil_values
7485
+ hash_data = { 'VAR1' => nil, 'VAR2' => 'value2' }
7486
+ @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7487
+ true])
7488
+ @hd.stubs(:variable_is_exportable).returns(true)
7489
+ @hd.stubs(:code_line_to_assign_a_variable).returns('VAR1=')
7490
+
7491
+ result = @hd.ux_block_eval_for_export(
7492
+ @bash_script_lines, @export,
7493
+ data: hash_data,
7494
+ force: false,
7495
+ silent: false
7496
+ )
7497
+
7498
+ command_result, exportable, new_lines = result
7499
+ assert_equal @mock_result, command_result
7500
+ assert_equal true, exportable
7501
+ assert_equal 2, new_lines.length
7502
+ end
7503
+
7504
+ # Edge case: unsupported input type (Array)
7505
+ def test_unsupported_input_type_array
7506
+ result = @hd.ux_block_eval_for_export(
7507
+ @bash_script_lines, @export,
7508
+ data: %w[item1 item2],
7509
+ force: false,
7510
+ silent: false
7511
+ )
7512
+
7513
+ command_result, exportable, new_lines = result
7514
+ assert_nil command_result # No processing for unsupported types
7515
+ assert_equal true, exportable # Initial value
7516
+ assert_equal 0, new_lines.length
7517
+ end
7518
+
7519
+ # Edge case: unsupported input type (custom object)
7520
+ def test_unsupported_input_type_object
7521
+ custom_object = Object.new
7522
+ result = @hd.ux_block_eval_for_export(
7523
+ @bash_script_lines, @export,
7524
+ data: custom_object,
7525
+ force: false,
7526
+ silent: false
7527
+ )
7528
+
7529
+ command_result, exportable, new_lines = result
7530
+ assert_nil command_result # No processing for unsupported types
7531
+ assert_equal true, exportable # Initial value
7532
+ assert_equal 0, new_lines.length
7533
+ end
7534
+
7535
+ # Test error handling - StandardError rescue
7536
+ def test_standard_error_rescue
7537
+ # Should not raise, but return nil due to rescue block
7538
+ result = @hd.ux_block_eval_for_export(
7539
+ @bash_script_lines, @export,
7540
+ data: 'test_data',
7541
+ force: false,
7542
+ silent: false
7543
+ )
7544
+
7545
+ command_result, = result
7546
+ assert_equal 127, command_result.exit_status
7547
+ end
7548
+
7549
+ # Test environment variable setting
7550
+ def test_environment_variable_setting
7551
+ @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7552
+ true])
7553
+ @hd.stubs(:variable_is_exportable).returns(true)
7554
+
7555
+ # Mock EnvInterface.set to verify it's called
7556
+ EnvInterface.expects(:set).with('TEST_VAR', 'test_output')
7557
+
7558
+ @hd.ux_block_eval_for_export(
7559
+ @bash_script_lines, @export,
7560
+ data: 'echo "test"',
7561
+ force: false,
7562
+ silent: false
7563
+ )
7564
+ end
7565
+
7566
+ # Test join_array_of_arrays is called correctly
7567
+ def test_join_array_of_arrays_called
7568
+ @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7569
+ true])
7570
+ @hd.stubs(:variable_is_exportable).returns(true)
7571
+
7572
+ # Mock join_array_of_arrays to verify it's called with correct parameters
7573
+ @hd.expects(:join_array_of_arrays).with(
7574
+ @bash_script_lines,
7575
+ ['test_data']
7576
+ ).returns(['combined_script_lines'])
7577
+
7578
+ @hd.ux_block_eval_for_export(
7579
+ @bash_script_lines, @export,
7580
+ data: 'test_data',
7581
+ force: false,
7582
+ printf_expand: false,
7583
+ silent: false
7584
+ )
7585
+ end
7586
+ end
6728
7587
  end # module MarkdownExec