markdown_exec 3.2.0 → 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 +27 -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 +2 -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 +120 -28
  42. data/lib/format_table.rb +56 -23
  43. data/lib/fout.rb +5 -0
  44. data/lib/hash_delegator.rb +1158 -347
  45. data/lib/markdown_exec/version.rb +1 -1
  46. data/lib/markdown_exec.rb +2 -0
  47. data/lib/mdoc.rb +13 -11
  48. data/lib/menu.src.yml +139 -34
  49. data/lib/menu.yml +116 -32
  50. data/lib/string_util.rb +80 -0
  51. data/lib/table_extractor.rb +170 -64
  52. data/lib/ww.rb +325 -29
  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,
@@ -1442,17 +1669,12 @@ module MarkdownExec
1442
1669
  # Skip processing for shell-type blocks
1443
1670
  next if exclude_types.include?(block.type)
1444
1671
 
1445
- begin
1446
-
1447
- # Scan each block name for matches of the pattern
1448
- count_named_group_occurrences_block_body_fix_indent(block).scan(pattern) do |(_, _variable_name)|
1449
- pattern.match($LAST_MATCH_INFO.to_s) # Reapply match for named groups
1450
- id = $LAST_MATCH_INFO[group_name]
1451
- occurrence_count[id] += 1
1452
- occurrence_expressions[id] = $LAST_MATCH_INFO['expression']
1453
- end
1454
- rescue Interrupt
1455
- binding.irb
1672
+ # Scan each block name for matches of the pattern
1673
+ count_named_group_occurrences_block_body_fix_indent(block).scan(pattern) do |(_, _variable_name)|
1674
+ pattern.match($LAST_MATCH_INFO.to_s) # Reapply match for named groups
1675
+ id = $LAST_MATCH_INFO[group_name]
1676
+ occurrence_count[id] += 1
1677
+ occurrence_expressions[id] = $LAST_MATCH_INFO['expression']
1456
1678
  end
1457
1679
  end
1458
1680
 
@@ -1518,7 +1740,9 @@ module MarkdownExec
1518
1740
  if wrap
1519
1741
  line_caps = line_caps.flat_map do |line_cap|
1520
1742
  text = line_cap[:text]
1521
- 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
+ )
1522
1746
 
1523
1747
  if text.length > screen_width_for_wrapping
1524
1748
  # Wrap this text and create line_cap objects for each part
@@ -1564,13 +1788,16 @@ module MarkdownExec
1564
1788
  decorated = apply_tree_decorations(
1565
1789
  oname, color_method, decor_patterns
1566
1790
  )
1567
-
1568
1791
  line_obj[:line] = line_obj[:indent] + line_obj[:text]
1569
1792
 
1570
1793
  if use_fcb
1571
1794
  fcb.center = center
1572
1795
  fcb.chrome = true
1573
- 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
1574
1801
  fcb.disabled = disabled ? TtyMenu::DISABLE : nil
1575
1802
  fcb.dname = line_obj[:indent] + decorated
1576
1803
  fcb.id = "#{id}.#{index}"
@@ -1588,7 +1815,12 @@ module MarkdownExec
1588
1815
  fcb = persist_fcb(
1589
1816
  center: center,
1590
1817
  chrome: true,
1591
- 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,
1592
1824
  disabled: disabled ? TtyMenu::DISABLE : nil,
1593
1825
  dname: line_obj[:indent] + decorated,
1594
1826
  id: "#{id}.#{index}",
@@ -1618,53 +1850,65 @@ module MarkdownExec
1618
1850
  # @param use_chrome [Boolean] Indicates if the chrome styling should
1619
1851
  # be applied.
1620
1852
  def create_and_add_chrome_blocks(blocks, fcb, id: '', init_ids: false)
1621
- chrome_block_criteria.each_with_index do |criteria, index|
1622
- unless @delegate_object[criteria[:match]].present? &&
1623
- (mbody = fcb.body[0].match @delegate_object[criteria[:match]])
1624
- 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
1625
1867
  end
1868
+ end
1626
1869
 
1627
- if block_given?
1628
- # expand references only if block is recognized (not a comment)
1629
- yield if block_given?
1870
+ return unless criteria
1630
1871
 
1631
- # parse multiline to capture output of variable expansion
1632
- mbody = fcb.body[0].match Regexp.new(
1633
- @delegate_object[criteria[:match]], Regexp::MULTILINE
1634
- )
1635
- end
1872
+ if block_given?
1873
+ # expand references only if block is recognized (not a comment)
1874
+ yield if block_given?
1636
1875
 
1637
- create_and_add_chrome_block(
1638
- blocks: blocks,
1639
- case_conversion: criteria[:case_conversion],
1640
- center: criteria[:center] &&
1641
- @delegate_object[criteria[:center]],
1642
-
1643
- collapse: case fcb.collapse_token
1644
- when COLLAPSIBLE_TOKEN_COLLAPSE
1645
- true
1646
- when COLLAPSIBLE_TOKEN_EXPAND
1647
- false
1648
- else
1649
- false
1650
- end,
1651
-
1652
- color_method: criteria[:color] &&
1653
- @delegate_object[criteria[:color]].to_sym,
1654
- decor_patterns:
1655
- @decor_patterns_from_delegate_object_for_block_create,
1656
- disabled: !(criteria[:collapsible] && @delegate_object[criteria[:collapsible]]),
1657
- fcb: fcb,
1658
- id: "#{id}.#{index}",
1659
- format_option: criteria[:format] &&
1660
- @delegate_object[criteria[:format]],
1661
- level: criteria[:level],
1662
- match_data: mbody,
1663
- type: criteria[:type],
1664
- 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
1665
1879
  )
1666
- break
1667
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
+ )
1668
1912
  end
1669
1913
 
1670
1914
  def create_divider(position, source_id: '')
@@ -1673,8 +1917,10 @@ module MarkdownExec
1673
1917
  else
1674
1918
  :menu_final_divider
1675
1919
  end
1676
- oname = format(@delegate_object[:menu_divider_format],
1677
- HashDelegator.safeval(@delegate_object[divider_key]))
1920
+ oname = format(
1921
+ @delegate_object[:menu_divider_format],
1922
+ HashDelegator.safeval(@delegate_object[divider_key])
1923
+ )
1678
1924
 
1679
1925
  persist_fcb(
1680
1926
  chrome: true,
@@ -2001,11 +2247,6 @@ module MarkdownExec
2001
2247
 
2002
2248
  LoadFileLinkState.new(LoadFile::REUSE, link_state)
2003
2249
 
2004
- # from CLI
2005
- elsif selected.nickname == @delegate_object[:menu_option_exit_name][:line]
2006
- debounce_reset
2007
- LoadFileLinkState.new(LoadFile::EXIT, link_state)
2008
-
2009
2250
  elsif @menu_user_clicked_back_link
2010
2251
  debounce_reset
2011
2252
  LoadFileLinkState.new(
@@ -2042,6 +2283,7 @@ module MarkdownExec
2042
2283
  selected,
2043
2284
  @dml_mdoc,
2044
2285
  inherited_code: @dml_link_state.inherited_lines,
2286
+ only_default: false,
2045
2287
  silent: true
2046
2288
  )
2047
2289
  ### TBD if command_result_w_e_t_nl.failure?
@@ -2159,16 +2401,19 @@ module MarkdownExec
2159
2401
  link_state: LinkState.new, block_source: {}
2160
2402
  )
2161
2403
  link_block_data = HashDelegator.parse_yaml_data_from_body(link_block_body)
2404
+
2162
2405
  ## collect blocks specified by block
2163
2406
  #
2164
2407
  if mdoc
2165
2408
  code_info = mdoc.collect_recursively_required_code(
2166
- anyname: selected.pub_name,
2409
+ anyname: selected_id_name(selected),
2410
+ block_source: block_source,
2167
2411
  label_format_above: @delegate_object[:shell_code_label_format_above],
2168
- label_format_below: @delegate_object[:shell_code_label_format_below],
2169
- 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
2170
2416
  )
2171
- code_lines = code_info[:code]
2172
2417
  block_names = code_info[:block_names]
2173
2418
  dependencies = code_info[:dependencies]
2174
2419
  else
@@ -2203,9 +2448,8 @@ module MarkdownExec
2203
2448
 
2204
2449
  # if an eval link block, evaluate code_lines and return its standard output
2205
2450
  #
2206
- if link_block_data.fetch(LinkKeys::EVAL,
2207
- false) || link_block_data.fetch(LinkKeys::EXEC,
2208
- false)
2451
+ if link_block_data.fetch(LinkKeys::EVAL, false) ||
2452
+ link_block_data.fetch(LinkKeys::EXEC, false)
2209
2453
  code_lines += link_block_data_eval(
2210
2454
  link_state, code_lines, selected, link_block_data,
2211
2455
  block_source: block_source,
@@ -2222,6 +2466,10 @@ module MarkdownExec
2222
2466
  nil
2223
2467
  ) || link_block_data.fetch(LinkKeys::BLOCK, nil) || ''
2224
2468
 
2469
+ code_lines = annotate_required_lines(
2470
+ 'blk:LINK', code_lines, block_name: selected.id
2471
+ )
2472
+
2225
2473
  if link_block_data[LinkKeys::RETURN]
2226
2474
  pop_add_current_code_to_head_and_trigger_load(
2227
2475
  link_state, block_names, code_lines,
@@ -2234,9 +2482,9 @@ module MarkdownExec
2234
2482
  curr_block_name: selected.pub_name,
2235
2483
  curr_document_filename: @delegate_object[:filename],
2236
2484
  inherited_block_names:
2237
- ((link_state&.inherited_block_names || []) + block_names).sort.uniq,
2485
+ ((link_state&.inherited_block_names || []) + block_names).sort.uniq,
2238
2486
  inherited_dependencies:
2239
- (link_state&.inherited_dependencies || {}).merge(dependencies || {}), ### merge, not replace, key data
2487
+ (link_state&.inherited_dependencies || {}).merge(dependencies || {}), ### merge, not replace, key data
2240
2488
  inherited_lines: HashDelegator.flatten_and_compact_arrays(
2241
2489
  link_state&.inherited_lines, code_lines
2242
2490
  ),
@@ -2244,7 +2492,12 @@ module MarkdownExec
2244
2492
  next_block_name: next_block_name,
2245
2493
  next_document_filename: next_document_filename,
2246
2494
  next_keep_code: next_keep_code,
2247
- 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
2248
2501
  )
2249
2502
  end
2250
2503
  end
@@ -2284,7 +2537,9 @@ module MarkdownExec
2284
2537
  block_data['view'] || view,
2285
2538
  NamedCaptureExtractor.extract_named_group_match_data(
2286
2539
  file.match(
2287
- Regexp.new(block_data['filename_pattern'] || filename_pattern)
2540
+ Regexp.new(
2541
+ block_data['filename_pattern'] || filename_pattern
2542
+ )
2288
2543
  )
2289
2544
  )
2290
2545
  ),
@@ -2311,9 +2566,11 @@ module MarkdownExec
2311
2566
  reason = 'default Load mode'
2312
2567
  mode = LoadMode::APPEND
2313
2568
  end
2569
+ wwt :state, '@dml_block_state:', @dml_block_state
2314
2570
 
2315
2571
  OpenStruct.new(
2316
- code: code,
2572
+ code: annotate_required_lines('blk:LOAD', code,
2573
+ block_name: selected.id),
2317
2574
  mode: mode,
2318
2575
  reason: reason
2319
2576
  )
@@ -2330,7 +2587,7 @@ module MarkdownExec
2330
2587
  def execute_block_type_port_code_lines(mdoc:, selected:, block_source:,
2331
2588
  link_state: LinkState.new)
2332
2589
  required = mdoc.collect_recursively_required_code(
2333
- anyname: selected.pub_name,
2590
+ anyname: selected_id_name(selected),
2334
2591
  label_format_above: @delegate_object[:shell_code_label_format_above],
2335
2592
  label_format_below: @delegate_object[:shell_code_label_format_below],
2336
2593
  block_source: block_source
@@ -2361,15 +2618,21 @@ module MarkdownExec
2361
2618
 
2362
2619
  if selected[:type] == BlockType::OPTS
2363
2620
  # body of blocks is returned as a list of lines to be read an YAML
2364
- 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
+ )
2365
2624
  else
2366
2625
  code_lines = if selected.type == BlockType::VARS
2367
2626
  code_from_vars_block_to_set_environment_variables(selected)
2368
2627
  else
2369
2628
  []
2370
2629
  end
2371
- HashDelegator.flatten_and_compact_arrays(link_state&.inherited_lines,
2372
- 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
+ )
2373
2636
  end
2374
2637
  end
2375
2638
 
@@ -2388,6 +2651,11 @@ module MarkdownExec
2388
2651
  save_filespec_from_expression(directory_glob).tap do |save_filespec|
2389
2652
  if save_filespec && save_filespec != exit_prompt
2390
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
+
2391
2659
  File.write(save_filespec,
2392
2660
  HashDelegator.join_code_lines(code_lines))
2393
2661
  rescue Errno::ENOENT
@@ -2430,8 +2698,10 @@ module MarkdownExec
2430
2698
  end
2431
2699
 
2432
2700
  # Handle stdin stream
2433
- input_thread = handle_stream(stream: $stdin,
2434
- file_type: ExecutionStreams::STD_IN) do |line|
2701
+ input_thread = handle_stream(
2702
+ stream: $stdin,
2703
+ file_type: ExecutionStreams::STD_IN
2704
+ ) do |line|
2435
2705
  stdin.puts(line)
2436
2706
  yield line, nil, nil, exec_thread if block_given?
2437
2707
  end
@@ -2560,6 +2830,7 @@ module MarkdownExec
2560
2830
  end
2561
2831
 
2562
2832
  def expand_references!(fcb, link_state)
2833
+ wwt :expand_references, 'fcb.id', fcb.id, 'link_state:', link_state
2563
2834
  # options expansions
2564
2835
  expand_variable_references!(
2565
2836
  blocks: [fcb],
@@ -2567,8 +2838,9 @@ module MarkdownExec
2567
2838
  group_name: :payload,
2568
2839
  initial_code_required: false,
2569
2840
  link_state: link_state,
2570
- pattern: @delegate_object[:option_expansion_expression_regexp].present? &&
2571
- 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])
2572
2844
  )
2573
2845
 
2574
2846
  # variable expansions
@@ -2577,7 +2849,8 @@ module MarkdownExec
2577
2849
  echo_formatter: lambda do |variable|
2578
2850
  %(echo "$#{variable}")
2579
2851
  end,
2580
- group_name: @delegate_object[:variable_expansion_name_capture_group]&.to_sym,
2852
+ group_name:
2853
+ @delegate_object[:variable_expansion_name_capture_group]&.to_sym,
2581
2854
  initial_code_required: false,
2582
2855
  link_state: link_state,
2583
2856
  pattern: options_variable_expansion_regexp
@@ -2587,7 +2860,8 @@ module MarkdownExec
2587
2860
  expand_variable_references!(
2588
2861
  blocks: [fcb],
2589
2862
  echo_formatter: lambda { |command| command },
2590
- group_name: @delegate_object[:command_substitution_name_capture_group]&.to_sym,
2863
+ group_name:
2864
+ @delegate_object[:command_substitution_name_capture_group]&.to_sym,
2591
2865
  initial_code_required: false,
2592
2866
  link_state: link_state,
2593
2867
  pattern: options_command_substitution_regexp
@@ -2630,29 +2904,37 @@ module MarkdownExec
2630
2904
  # no return
2631
2905
  end
2632
2906
 
2633
- def export_echo_with_code(
2634
- 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
2635
2915
  )
2636
- wwp
2637
2916
  exportable = true
2638
2917
  command_result = nil
2639
2918
  new_lines = []
2640
- export_string = string.nil? ? export.echo : string
2919
+ export_string = string.nil? ? data : string
2920
+ expander = ->(expression) { %(printf '%s' "#{expression}") }
2921
+
2641
2922
  case export_string
2642
2923
  when String, Integer, Float, TrueClass, FalseClass
2643
2924
  command_result, exportable, = output_from_adhoc_bash_script_file(
2644
2925
  join_array_of_arrays(
2645
2926
  bash_script_lines,
2646
- %(printf '%s' "#{export_string}")
2927
+ printf_expand ? expander.call(export_string) : [export_string]
2647
2928
  ),
2648
2929
  export,
2649
2930
  force: force
2650
2931
  )
2651
2932
  if command_result.exit_status == EXIT_STATUS_REQUIRED_EMPTY
2652
2933
  exportable = false
2653
- command_result.warning = warning_required_empty(export) unless silent
2934
+ unless silent
2935
+ command_result.warning = warning_required_empty(export)
2936
+ end
2654
2937
  else
2655
- ### TBD validate/transform?
2656
2938
  # store the transformed value in ENV
2657
2939
  EnvInterface.set(export.name, command_result.stdout.to_s)
2658
2940
 
@@ -2669,17 +2951,17 @@ module MarkdownExec
2669
2951
  join_array_of_arrays(
2670
2952
  bash_script_lines,
2671
2953
  required_lines,
2672
- %(printf '%s' "#{expression}")
2954
+ printf_expand ? expander.call(expression) : [expression]
2673
2955
  ),
2674
2956
  export,
2675
2957
  force: force
2676
2958
  )
2677
2959
  if command_result.exit_status == EXIT_STATUS_REQUIRED_EMPTY
2678
- command_result.warning = warning_required_empty(export) unless silent
2960
+ unless silent
2961
+ command_result.warning = warning_required_empty(export)
2962
+ end
2679
2963
  else
2680
2964
  transformed = command_result.stdout.to_s
2681
- ### TBD validate/transform?
2682
- # transformed = if command_result_w_e_t_nl.transformable transform_export_value(name_force[:text], export) else name_force[:text] end
2683
2965
 
2684
2966
  # code for subsequent expression evaluations
2685
2967
  required_lines << code_line_to_assign_a_variable(
@@ -2691,13 +2973,19 @@ module MarkdownExec
2691
2973
  EnvInterface.set(name, transformed)
2692
2974
 
2693
2975
  new_lines << { name: name, force: force,
2694
- text: command_result.stdout }
2976
+ text: command_result.stdout }
2695
2977
  end
2696
2978
  end
2979
+
2980
+ break if first_only
2697
2981
  end
2982
+ else
2983
+ # do nothing
2698
2984
  end
2699
2985
 
2700
2986
  [command_result, exportable, new_lines]
2987
+ rescue StandardError
2988
+ wwe bash_script_lines, export, force, silent, string
2701
2989
  end
2702
2990
 
2703
2991
  # Retrieves a specific data symbol from the delegate object,
@@ -2737,8 +3025,13 @@ module MarkdownExec
2737
3025
  # fallback to @dml_menu_blocks if not found.
2738
3026
  def find_block_by_name(blocks, block_name)
2739
3027
  match_block = ->(item) do
2740
- [item.pub_name, item.nickname,
2741
- 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)
2742
3035
  end
2743
3036
 
2744
3037
  @dml_blocks_in_file.find(&match_block) ||
@@ -2785,7 +3078,9 @@ module MarkdownExec
2785
3078
  end
2786
3079
 
2787
3080
  def format_echo_command(payload)
2788
- payload_match = payload.match(@delegate_object[:option_expansion_payload_regexp])
3081
+ payload_match = payload.match(
3082
+ @delegate_object[:option_expansion_payload_regexp]
3083
+ )
2789
3084
  variable = payload_match[:option]
2790
3085
  property = payload_match[:property]
2791
3086
 
@@ -2833,6 +3128,20 @@ module MarkdownExec
2833
3128
  string_send_color(formatted_string, color_sym)
2834
3129
  end
2835
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
+
2836
3145
  # Expand expression if it contains format specifiers
2837
3146
  def formatted_expression(expr)
2838
3147
  expr.include?('%{') ? format_expression(expr) : expr
@@ -3020,6 +3329,7 @@ module MarkdownExec
3020
3329
  ) do |nested_line|
3021
3330
  next if nested_line.nil?
3022
3331
 
3332
+ wwt :iterlines, 'nested_line:', nested_line
3023
3333
  update_line_and_block_state(
3024
3334
  nested_line, state, selected_types,
3025
3335
  source_id: "ItrBlkFrmNstFls:#{index}¤#{nested_line.filename}:#{nested_line.index}",
@@ -3028,6 +3338,8 @@ module MarkdownExec
3028
3338
 
3029
3339
  index += 1
3030
3340
  end
3341
+ rescue StandardError
3342
+ wwe $!, 'state:', state, 'nested_line:', nested_line
3031
3343
  end
3032
3344
 
3033
3345
  def iter_source_blocks(source, source_id: nil, &block)
@@ -3062,8 +3374,9 @@ module MarkdownExec
3062
3374
 
3063
3375
  def link_block_data_eval(link_state, code_lines, selected, link_block_data,
3064
3376
  block_source:, shell:)
3065
- all_code = HashDelegator.flatten_and_compact_arrays(link_state&.inherited_lines,
3066
- code_lines)
3377
+ all_code = HashDelegator.flatten_and_compact_arrays(
3378
+ link_state&.inherited_lines, code_lines
3379
+ )
3067
3380
  output_lines = []
3068
3381
 
3069
3382
  Tempfile.open do |file|
@@ -3197,9 +3510,10 @@ module MarkdownExec
3197
3510
 
3198
3511
  # Loads and updates auto options for document blocks if the current filename has changed.
3199
3512
  #
3200
- # This method checks if the delegate object specifies a document load options block name and if the filename
3201
- # has been updated. It then selects the appropriate blocks, collects their dependencies, processes their
3202
- # 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.
3203
3517
  #
3204
3518
  # @param all_blocks [Array] An array of all block elements.
3205
3519
  # @param mdoc [Object] The document object managing dependencies and options.
@@ -3237,27 +3551,6 @@ module MarkdownExec
3237
3551
  true
3238
3552
  end
3239
3553
 
3240
- def load_auto_vars_block(
3241
- all_blocks,
3242
- block_name: @delegate_object[:document_load_vars_block_name]
3243
- )
3244
- unless block_name.present? &&
3245
- @vars_most_recent_filename != @delegate_object[:filename]
3246
- return
3247
- end
3248
-
3249
- blocks = HashDelegator.block_select(all_blocks, :oname, block_name)
3250
- return if blocks.empty?
3251
-
3252
- @vars_most_recent_filename = @delegate_object[:filename]
3253
-
3254
- (blocks.each.with_object([]) do |block, merged_options|
3255
- merged_options.push(
3256
- code_from_vars_block_to_set_environment_variables(block)
3257
- )
3258
- end).to_a
3259
- end
3260
-
3261
3554
  def load_cli_or_user_selected_block(all_blocks: [], menu_blocks: [],
3262
3555
  prior_answer: nil)
3263
3556
  if @delegate_object[:block_name].present?
@@ -3278,30 +3571,6 @@ module MarkdownExec
3278
3571
  SelectedBlockMenuState.new(block, source, state)
3279
3572
  end
3280
3573
 
3281
- def load_document_shell_block(all_blocks, mdoc: nil)
3282
- block_name = @delegate_object[:document_load_shell_block_name]
3283
- unless block_name.present? &&
3284
- @shell_most_recent_filename != @delegate_object[:filename]
3285
- return
3286
- end
3287
-
3288
- fcb = HashDelegator.block_find(all_blocks, :oname, block_name)
3289
- return unless fcb
3290
-
3291
- @shell_most_recent_filename = @delegate_object[:filename]
3292
-
3293
- if mdoc
3294
- mdoc.collect_recursively_required_code(
3295
- anyname: fcb.id,####
3296
- label_format_above: @delegate_object[:shell_code_label_format_above],
3297
- label_format_below: @delegate_object[:shell_code_label_format_below],
3298
- block_source: block_source
3299
- )[:code]
3300
- else
3301
- fcb.body
3302
- end
3303
- end
3304
-
3305
3574
  # format + glob + select for file in load block
3306
3575
  # name has references to ENV vars and doc and batch vars
3307
3576
  # incl. timestamp
@@ -3393,32 +3662,21 @@ module MarkdownExec
3393
3662
  reload_blocks = true
3394
3663
  end
3395
3664
 
3396
- # load document shell block
3397
- #
3398
- if (code_lines = load_document_shell_block(all_blocks, mdoc: mdoc))
3399
- next_state_set_code(nil, link_state, code_lines)
3400
- link_state.inherited_lines = code_lines
3401
- reload_blocks = true
3402
- end
3403
-
3404
- # load document ux block
3665
+ # return code resulting from evaluating all SHELL, UX, VARS blocks;
3666
+ # each set in sequence; with its own order
3405
3667
  #
3406
- if (code_lines = code_from_automatic_ux_blocks(all_blocks, mdoc))
3407
- new_code = HashDelegator.flatten_and_compact_arrays(link_state.inherited_lines,
3408
- code_lines)
3409
- next_state_set_code(nil, link_state, new_code)
3410
- link_state.inherited_lines = new_code
3411
- reload_blocks = true
3412
- end
3413
-
3414
- # load document vars block
3415
- #
3416
- if (code_lines = load_auto_vars_block(all_blocks))
3417
- new_code = HashDelegator.flatten_and_compact_arrays(link_state.inherited_lines,
3418
- 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
3419
3675
  next_state_set_code(nil, link_state, new_code)
3420
3676
  link_state.inherited_lines = new_code
3421
3677
  reload_blocks = true
3678
+ else
3679
+ link_state&.inherited_lines
3422
3680
  end
3423
3681
 
3424
3682
  if reload_blocks
@@ -3493,7 +3751,7 @@ module MarkdownExec
3493
3751
  end
3494
3752
  end
3495
3753
 
3496
- def menu_add_disabled_option(document_glob)
3754
+ def menu_add_disabled_option(document_glob, id)
3497
3755
  raise unless document_glob.present?
3498
3756
  raise if @dml_menu_blocks.nil?
3499
3757
 
@@ -3503,13 +3761,16 @@ module MarkdownExec
3503
3761
  #
3504
3762
  return unless block.nil?
3505
3763
 
3764
+ dname = HashDelegator.new(@delegate_object).string_send_color(
3765
+ document_glob, :menu_inherited_lines_color
3766
+ )
3767
+
3506
3768
  chrome_block = persist_fcb(
3507
3769
  chrome: true,
3508
3770
  disabled: TtyMenu::DISABLE,
3509
- dname: HashDelegator.new(@delegate_object).string_send_color(
3510
- document_glob, :menu_inherited_lines_color
3511
- ),
3771
+ dname: dname,
3512
3772
  # 2025-01-03 menu item is disabled ∴ does not need a recall id
3773
+ id: id,
3513
3774
  oname: formatted_name
3514
3775
  )
3515
3776
 
@@ -3779,7 +4040,8 @@ module MarkdownExec
3779
4040
  end
3780
4041
  end
3781
4042
 
3782
- # 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.
3783
4045
  # It updates the history state for the next block.
3784
4046
  #
3785
4047
  # @return [LinkState] An object indicating the state for
@@ -3834,12 +4096,13 @@ module MarkdownExec
3834
4096
  multiline = fcb.indented_decorated ||
3835
4097
  (fcb.indent + (fcb.s1decorated || fcb.dname))
3836
4098
 
3837
- 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|
3838
4101
  if fcb.fetch(:disabled, nil).nil?
3839
4102
  index.zero? ? active : inactive
3840
4103
  else
3841
4104
  inactive
3842
- end + line.chomp
4105
+ end + line
3843
4106
  end.join("\n")
3844
4107
 
3845
4108
  fcb.value = fcb.id || fcb.name.split("\n").first
@@ -3885,8 +4148,10 @@ module MarkdownExec
3885
4148
  raise StandardError, $!
3886
4149
  end
3887
4150
 
3888
- def process_string_array(arr, begin_pattern: nil, end_pattern: nil, scan1: nil,
3889
- format1: nil, name: '')
4151
+ def process_string_array(
4152
+ arr, begin_pattern: nil, end_pattern: nil, scan1: nil,
4153
+ format1: nil, name: ''
4154
+ )
3890
4155
  in_block = !begin_pattern.present?
3891
4156
  collected_lines = []
3892
4157
 
@@ -3939,6 +4204,9 @@ module MarkdownExec
3939
4204
 
3940
4205
  @allowed_execution_block = @prior_execution_block
3941
4206
  true
4207
+ rescue TTY::Reader::InputInterrupt
4208
+ # treat as denial
4209
+ false
3942
4210
  end
3943
4211
 
3944
4212
  def prompt_for_command(prompt)
@@ -4022,6 +4290,9 @@ module MarkdownExec
4022
4290
  end
4023
4291
 
4024
4292
  sel == MenuOptions::YES
4293
+ rescue TTY::Reader::InputInterrupt
4294
+ # treat as denial
4295
+ false
4025
4296
  end
4026
4297
 
4027
4298
  def prompt_margin_left_text
@@ -4046,7 +4317,14 @@ module MarkdownExec
4046
4317
  menu.choice @delegate_object[:prompt_yes]
4047
4318
  menu.choice @delegate_object[:prompt_exit]
4048
4319
  end
4049
- 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
4050
4328
  end
4051
4329
 
4052
4330
  # public
@@ -4075,6 +4353,9 @@ module MarkdownExec
4075
4353
  end
4076
4354
  end
4077
4355
  end
4356
+ rescue TTY::Reader::InputInterrupt
4357
+ # treat as no selection
4358
+ nil
4078
4359
  end
4079
4360
 
4080
4361
  # user prompt to exit if the menu will be displayed again
@@ -4111,15 +4392,6 @@ module MarkdownExec
4111
4392
  end
4112
4393
  end
4113
4394
 
4114
- # # Handle expression with wildcard characters
4115
- # # allow user to select or enter
4116
- # def puts_gets_oprompt_(filespec)
4117
- # puts format(@delegate_object[:prompt_show_expr_format],
4118
- # { expr: filespec })
4119
- # puts @delegate_object[:prompt_enter_filespec]
4120
- # gets.chomp
4121
- # end
4122
-
4123
4395
  def read_saved_assets_for_history_table(
4124
4396
  asset: nil,
4125
4397
  filename: nil,
@@ -4353,7 +4625,9 @@ module MarkdownExec
4353
4625
  required_lines:, selected:, shell:
4354
4626
  )
4355
4627
  write_command_file(
4356
- 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)
4357
4631
  )
4358
4632
  @fout.fout "File saved: #{@run_state.saved_filespec}"
4359
4633
  end
@@ -4401,7 +4675,8 @@ module MarkdownExec
4401
4675
 
4402
4676
  def screen_width_for_table
4403
4677
  # menu adds newline after some lines if sized to the edge
4404
- 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
4405
4680
  end
4406
4681
 
4407
4682
  def screen_width_for_wrapping
@@ -4475,14 +4750,12 @@ module MarkdownExec
4475
4750
  # no enabled options in page
4476
4751
  return
4477
4752
  end
4478
- ww 'selection', selection
4479
4753
 
4480
4754
  menu_list = opts.fetch(:match_dml, true) ? @dml_menu_blocks : menu_items
4481
4755
  menu_list ||= tty_menu_items
4482
4756
  selected = menu_list.find do |item|
4483
4757
  if item.instance_of?(Hash)
4484
- ### [item[:id], item[:name], item[:dname]].include?(selection)
4485
- item[:id] == selection
4758
+ [item[:id], item[:name], item[:dname]].include?(selection)
4486
4759
  elsif item.instance_of?(MarkdownExec::FCB)
4487
4760
  item.id == selection
4488
4761
  else
@@ -4504,10 +4777,14 @@ module MarkdownExec
4504
4777
  exit 1
4505
4778
  end
4506
4779
 
4507
- 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]
4508
4783
  selected.option = selection
4509
4784
  selected.type = BlockType::LINK
4510
- 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]
4511
4788
  selected.option = selection
4512
4789
  else
4513
4790
  selected.selected = selection
@@ -4648,6 +4925,7 @@ module MarkdownExec
4648
4925
  # color_sym is not found in @delegate_object.
4649
4926
  # @return [String] The string with the applied color method.
4650
4927
  def string_send_color(string, color_sym)
4928
+ ### accept string with color as well as symbol for color_hash
4651
4929
  HashDelegator.apply_color_from_hash(string, @delegate_object, color_sym)
4652
4930
  end
4653
4931
 
@@ -4737,12 +5015,23 @@ module MarkdownExec
4737
5015
  @delegate_object[:menu_include_imported_notes]
4738
5016
  # add line if it is depth 0 or option allows it
4739
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
4740
5026
  HashDelegator.yield_line_if_selected(
4741
5027
  line, selected_types,
4742
5028
  all_fcbs: @fcb_store,
5029
+ criteria: criteria,
4743
5030
  source_id: source_id, &block
4744
5031
  )
4745
5032
  end
5033
+ rescue StandardError
5034
+ wwe $!, 'state:', state, 'nested_line:', nested_line
4746
5035
  end
4747
5036
 
4748
5037
  ## apply options to current state
@@ -4775,10 +5064,13 @@ module MarkdownExec
4775
5064
 
4776
5065
  case export.allow
4777
5066
  when :echo, ExportValueSource::ECHO
4778
- command_result, exportable, new_lines = export_echo_with_code(
5067
+ command_result, exportable, new_lines = ux_block_eval_for_export(
4779
5068
  bash_script_lines,
4780
5069
  export,
5070
+ data: export.echo,
5071
+ first_only: true,
4781
5072
  force: force,
5073
+ printf_expand: true,
4782
5074
  silent: silent
4783
5075
  )
4784
5076
 
@@ -4786,15 +5078,20 @@ module MarkdownExec
4786
5078
  command_result
4787
5079
  else
4788
5080
  command_result = CommandResult.new(
4789
- 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
+ )
4790
5084
  )
4791
5085
  end
4792
5086
 
4793
5087
  when :exec, UxActSource::EXEC
4794
- command_result, = output_from_adhoc_bash_script_file(
4795
- join_array_of_arrays(bash_script_lines, export.exec),
5088
+ command_result, exportable, new_lines = ux_block_eval_for_export(
5089
+ bash_script_lines,
4796
5090
  export,
4797
- force: force
5091
+ data: export.exec,
5092
+ first_only: true,
5093
+ force: force,
5094
+ silent: silent
4798
5095
  )
4799
5096
 
4800
5097
  if command_result.exit_status == EXIT_STATUS_REQUIRED_EMPTY
@@ -4809,10 +5106,13 @@ module MarkdownExec
4809
5106
 
4810
5107
  else
4811
5108
  export_init = menu_from_list_with_back(export.allow)
4812
- command_result, exportable, new_lines = export_echo_with_code(
5109
+ command_result, exportable, new_lines = ux_block_eval_for_export(
4813
5110
  [assign_key_value_in_bash(export.name, export_init)],
4814
5111
  export,
5112
+ data: export.echo,
5113
+ first_only: true,
4815
5114
  force: force,
5115
+ printf_expand: true,
4816
5116
  silent: silent,
4817
5117
  string: export_init
4818
5118
  )
@@ -4820,11 +5120,12 @@ module MarkdownExec
4820
5120
  end
4821
5121
 
4822
5122
  when :echo, UxActSource::ECHO
4823
- wwp
4824
- command_result, exportable, new_lines = export_echo_with_code(
5123
+ command_result, exportable, new_lines = ux_block_eval_for_export(
4825
5124
  bash_script_lines,
4826
5125
  export,
5126
+ data: export.echo,
4827
5127
  force: force,
5128
+ printf_expand: true,
4828
5129
  silent: silent
4829
5130
  )
4830
5131
 
@@ -4854,10 +5155,12 @@ module MarkdownExec
4854
5155
  command_result = CommandResult.new(stdout: output)
4855
5156
 
4856
5157
  when :exec, UxActSource::EXEC
4857
- command_result, exportable, new_lines = output_from_adhoc_bash_script_file(
4858
- join_array_of_arrays(bash_script_lines, export.exec),
5158
+ command_result, exportable, new_lines = ux_block_eval_for_export(
5159
+ bash_script_lines,
4859
5160
  export,
4860
- force: force
5161
+ data: export.exec,
5162
+ force: force,
5163
+ silent: silent
4861
5164
  )
4862
5165
 
4863
5166
  else
@@ -4875,6 +5178,8 @@ module MarkdownExec
4875
5178
  command_result.transformable = transformable
4876
5179
  command_result.new_lines = new_lines
4877
5180
  command_result
5181
+ rescue StandardError
5182
+ wwe bash_script_lines, export, exit_prompt
4878
5183
  end
4879
5184
 
4880
5185
  def ux_block_export_automatic(
@@ -4898,36 +5203,47 @@ module MarkdownExec
4898
5203
 
4899
5204
  case export.allow
4900
5205
  when :echo, ExportValueSource::ECHO
4901
- cr_echo, = output_from_adhoc_bash_script_file(
4902
- join_array_of_arrays(
4903
- bash_script_lines,
4904
- %(printf '%s' "#{export.echo}")
4905
- ),
5206
+ cr_echo, = ux_block_eval_for_export(
5207
+ bash_script_lines,
4906
5208
  export,
4907
- force: force
5209
+ data: export.echo,
5210
+ first_only: true,
5211
+ force: force,
5212
+ printf_expand: true,
5213
+ silent: silent
4908
5214
  )
4909
5215
  export_init = cr_echo.stdout.split("\n").first
4910
- command_result, exportable, new_lines = export_echo_with_code(
5216
+
5217
+ command_result, exportable, new_lines = ux_block_eval_for_export(
4911
5218
  [assign_key_value_in_bash(export.name, export_init)],
4912
5219
  export,
5220
+ data: export.echo,
5221
+ first_only: true,
4913
5222
  force: force,
5223
+ printf_expand: true,
4914
5224
  silent: silent,
4915
5225
  string: export_init
4916
5226
  )
4917
5227
 
4918
5228
  when :exec, ExportValueSource::EXEC
4919
5229
  # extract first line from 'exec' output
4920
- command_result, exportable, new_lines = output_from_adhoc_bash_script_file(
4921
- join_array_of_arrays(bash_script_lines, export.exec),
5230
+ command_result, exportable, new_lines = ux_block_eval_for_export(
5231
+ bash_script_lines,
4922
5232
  export,
4923
- force: force
5233
+ data: export.exec,
5234
+ first_only: true,
5235
+ force: force,
5236
+ silent: silent
4924
5237
  )
5238
+
4925
5239
  unless command_result.failure?
4926
5240
  export_init = command_result.stdout.split("\n").first
4927
- command_result, exportable, new_lines = export_echo_with_code(
5241
+ command_result, exportable, new_lines = ux_block_eval_for_export(
4928
5242
  [assign_key_value_in_bash(export.name, export_init)],
4929
5243
  export,
5244
+ data: export.exec,
4930
5245
  force: force,
5246
+ printf_expand: true,
4931
5247
  silent: silent,
4932
5248
  string: export_init
4933
5249
  )
@@ -4936,10 +5252,12 @@ module MarkdownExec
4936
5252
  else
4937
5253
  # first item from 'allow' list
4938
5254
  export_init = export.allow.first
4939
- command_result, exportable, new_lines = export_echo_with_code(
5255
+ command_result, exportable, new_lines = ux_block_eval_for_export(
4940
5256
  [assign_key_value_in_bash(export.name, export_init)],
4941
5257
  export,
5258
+ data: export.allow,
4942
5259
  force: force,
5260
+ printf_expand: true,
4943
5261
  silent: silent,
4944
5262
  string: export_init
4945
5263
  )
@@ -4953,32 +5271,37 @@ module MarkdownExec
4953
5271
  when :echo, UxActSource::ECHO
4954
5272
  raise unless export.echo.present?
4955
5273
 
4956
- command_result, exportable, new_lines = export_echo_with_code(
5274
+ command_result, exportable, new_lines = ux_block_eval_for_export(
4957
5275
  bash_script_lines,
4958
5276
  export,
5277
+ data: export.echo,
4959
5278
  force: force,
5279
+ printf_expand: true,
4960
5280
  silent: silent
4961
5281
  )
4962
5282
 
4963
5283
  when :exec, UxActSource::EXEC
4964
5284
  raise unless export.exec.present?
4965
5285
 
4966
- command_result, exportable, new_lines = output_from_adhoc_bash_script_file(
4967
- join_array_of_arrays(bash_script_lines, export.exec),
5286
+ command_result, exportable, new_lines = ux_block_eval_for_export(
5287
+ bash_script_lines,
4968
5288
  export,
4969
- force: force
5289
+ data: export.exec,
5290
+ force: force,
5291
+ silent: silent
4970
5292
  )
4971
5293
 
4972
5294
  else
4973
5295
  export_init = export.init.to_s
4974
- command_result, exportable, new_lines = export_echo_with_code(
5296
+ command_result, exportable, new_lines = ux_block_eval_for_export(
4975
5297
  [assign_key_value_in_bash(export.name, export_init)],
4976
5298
  export,
5299
+ data: export.exec,
4977
5300
  force: force,
5301
+ printf_expand: true,
4978
5302
  silent: silent,
4979
5303
  string: export_init
4980
5304
  )
4981
- # raise "Unknown FCB.init_source(export) #{FCB.init_source(export)}"
4982
5305
  end
4983
5306
 
4984
5307
  # add message for required variables
@@ -4991,6 +5314,8 @@ module MarkdownExec
4991
5314
  command_result.transformable = transformable
4992
5315
  command_result.new_lines = new_lines
4993
5316
  command_result
5317
+ rescue StandardError
5318
+ wwe bash_script_lines, export
4994
5319
  end
4995
5320
 
4996
5321
  # true if the variable is exported in a series of evaluations
@@ -5001,7 +5326,7 @@ module MarkdownExec
5001
5326
  return true if local_name_pattern.empty?
5002
5327
 
5003
5328
  # export if it its name does not match the rule
5004
- !(name =~ Regexp.new(local_name_pattern))
5329
+ name !~ Regexp.new(local_name_pattern)
5005
5330
  end
5006
5331
 
5007
5332
  def vux_await_user_selection(prior_answer: @dml_block_selection)
@@ -5020,17 +5345,119 @@ module MarkdownExec
5020
5345
  end
5021
5346
 
5022
5347
  def vux_clear_menu_state
5348
+ @dml_block_selection = @dml_block_state.block
5023
5349
  @dml_block_state = SelectedBlockMenuState.new
5024
5350
  @delegate_object[:block_name] = nil
5025
5351
  end
5026
5352
 
5027
5353
  def vux_edit_inherited
5028
5354
  edited = edit_text(@dml_link_state.inherited_lines_block)
5029
- @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]
5030
5409
  end
5031
5410
 
5032
5411
  def vux_execute_and_prompt(block_name)
5033
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
+
5034
5461
  if @dml_block_state.block &&
5035
5462
  @dml_block_state.block.type == BlockType::OPTS
5036
5463
  debounce_reset
@@ -5071,62 +5498,12 @@ module MarkdownExec
5071
5498
  end
5072
5499
 
5073
5500
  def vux_execute_block_per_type(block_name, formatted_choice_ostructs)
5074
- case block_name
5075
- when formatted_choice_ostructs[:back].pub_name
5076
- debounce_reset
5077
- vux_navigate_back_for_ls
5078
-
5079
- when formatted_choice_ostructs[:edit].pub_name
5080
- debounce_reset
5081
- vux_edit_inherited
5082
- return :break if pause_user_exit
5083
-
5084
- InputSequencer.next_link_state(prior_block_was_link: true)
5085
-
5086
- when formatted_choice_ostructs[:history].pub_name
5087
- debounce_reset
5088
- return :break unless (files_table_rows = vux_history_files_table_rows)
5089
-
5090
- execute_history_select(files_table_rows, stream: $stderr)
5091
- return :break if pause_user_exit
5092
-
5093
- InputSequencer.next_link_state(prior_block_was_link: true)
5094
-
5095
- when formatted_choice_ostructs[:load].pub_name
5096
- debounce_reset
5097
- vux_load_inherited
5098
- return :break if pause_user_exit
5099
-
5100
- InputSequencer.next_link_state(prior_block_was_link: true)
5101
-
5102
- when formatted_choice_ostructs[:save].pub_name
5103
- debounce_reset
5104
- return :break if execute_inherited_save == :break
5105
-
5106
- InputSequencer.next_link_state(prior_block_was_link: true)
5107
-
5108
- when formatted_choice_ostructs[:shell].pub_name
5109
- debounce_reset
5110
- vux_input_and_execute_shell_commands(stream: $stderr, shell: shell)
5111
- return :break if pause_user_exit
5112
-
5113
- InputSequencer.next_link_state(prior_block_was_link: true)
5114
-
5115
- when formatted_choice_ostructs[:view].pub_name
5116
- debounce_reset
5117
- vux_view_inherited(stream: $stderr)
5118
- return :break if pause_user_exit
5501
+ return :break if vux_execute_and_prompt(block_name) == :break
5119
5502
 
5120
- InputSequencer.next_link_state(prior_block_was_link: true)
5121
-
5122
- else
5123
- return :break if vux_execute_and_prompt(block_name) == :break
5124
-
5125
- InputSequencer.next_link_state(
5126
- block_name: @dml_link_state.block_name,
5127
- prior_block_was_link: @dml_block_state.block.type != BlockType::SHELL
5128
- )
5129
- 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
+ )
5130
5507
  end
5131
5508
 
5132
5509
  def vux_formatted_names_for_state_chrome_blocks(
@@ -5276,10 +5653,13 @@ module MarkdownExec
5276
5653
  block_list
5277
5654
  ).run do |msg, data|
5278
5655
  count += 1
5656
+ wwt :id, 'count:', count, 'msg:', msg, 'data:', data
5279
5657
  case msg
5280
5658
  when :parse_document # once for each menu
5281
5659
  count = 0
5282
- vux_parse_document(source_id: "#{@delegate_object[:filename]}¤VuxMainLoop®PrsDoc")
5660
+ vux_parse_document(
5661
+ source_id: "#{@delegate_object[:filename]}¤VuxMainLoop®PrsDoc"
5662
+ )
5283
5663
  vux_menu_append_history_files(
5284
5664
  formatted_choice_ostructs,
5285
5665
  source_id: "#{@delegate_object[:filename]}¤VuxMainLoop®HstFls"
@@ -5293,8 +5673,19 @@ module MarkdownExec
5293
5673
  when :end_of_cli
5294
5674
  # yield :end_of_cli, @delegate_object
5295
5675
 
5296
- if @delegate_object[:list_blocks]
5297
- 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
+ )
5298
5689
  :exit
5299
5690
  end
5300
5691
 
@@ -5365,7 +5756,7 @@ module MarkdownExec
5365
5756
 
5366
5757
  # add menu items (glob, load, save) and enable selectively
5367
5758
  if files.count.positive? || lines_count.positive?
5368
- menu_add_disabled_option(document_glob)
5759
+ menu_add_disabled_option(document_glob, "#{source_id}_vmahf_glob")
5369
5760
  end
5370
5761
  if files.count.positive?
5371
5762
  dml_menu_append_chrome_item(
@@ -5446,14 +5837,7 @@ module MarkdownExec
5446
5837
 
5447
5838
  def vux_publish_block_name_for_external_automation(block_name)
5448
5839
  publish_for_external_automation(
5449
- message: format(
5450
- @delegate_object[:publish_block_name_format],
5451
- { block: block_name,
5452
- document: @delegate_object[:filename],
5453
- time: Time.now.utc.strftime(
5454
- @delegate_object[:publish_time_format]
5455
- ) }
5456
- )
5840
+ message: formatted_block_name(block_name)
5457
5841
  )
5458
5842
  end
5459
5843
 
@@ -5489,7 +5873,7 @@ module MarkdownExec
5489
5873
  return :break if @dml_block_state.block.nil? # no block matched
5490
5874
  end
5491
5875
  # puts "! - Executing block: #{data}"
5492
- @dml_block_state.block&.pub_name
5876
+ @dml_block_state.block&.id
5493
5877
  end
5494
5878
 
5495
5879
  def vux_view_inherited(stream:)
@@ -5532,11 +5916,12 @@ module MarkdownExec
5532
5916
  nil
5533
5917
  when String
5534
5918
  menu_blocks.find do |block|
5535
- block.dname.include?(prior_answer)
5919
+ block.id == prior_answer
5536
5920
  end&.name
5537
5921
  when Struct, MarkdownExec::FCB
5538
5922
  if prior_answer.id
5539
- # when switching documents, the prior answer will not be found
5923
+ # when switching documents,
5924
+ # the prior answer will not be found
5540
5925
  (menu_blocks.find_index do |block|
5541
5926
  block[:id] == prior_answer.id
5542
5927
  end || 0) + 1
@@ -5638,8 +6023,10 @@ module MarkdownExec
5638
6023
  if save_expr.present?
5639
6024
  save_filespec = save_filespec_from_expression(save_expr)
5640
6025
  if save_filespec.present?
5641
- File.write(save_filespec,
5642
- HashDelegator.join_code_lines(link_state&.inherited_lines))
6026
+ File.write(
6027
+ save_filespec,
6028
+ HashDelegator.join_code_lines(link_state&.inherited_lines)
6029
+ )
5643
6030
  @delegate_object[:filename]
5644
6031
  else
5645
6032
  link_block_data[LinkKeys::FILE] || @delegate_object[:filename]
@@ -5650,6 +6037,7 @@ module MarkdownExec
5650
6037
  end
5651
6038
 
5652
6039
  def safe_yaml_load(body)
6040
+ caller.deref(12)
5653
6041
  return {} unless body&.present?
5654
6042
 
5655
6043
  YAML.load(body.to_s)
@@ -6773,4 +7161,427 @@ module MarkdownExec
6773
7161
  end
6774
7162
  end
6775
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
6776
7587
  end # module MarkdownExec