markdown_exec 3.5.1 → 3.5.2

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 (105) hide show
  1. checksums.yaml +4 -4
  2. data/.ai-agent-instructions +54 -0
  3. data/.cursorrules +198 -0
  4. data/.rubocop.wide.yml +5 -0
  5. data/.rubocop.yml +7 -2
  6. data/CHANGELOG.md +12 -1
  7. data/Gemfile.lock +1 -1
  8. data/Rakefile +2 -0
  9. data/ai-principles.md +516 -0
  10. data/architecture-decisions.md +190 -0
  11. data/bats/block-hide.bats +1 -1
  12. data/bats/block-type-bash.bats +5 -5
  13. data/bats/block-type-link.bats +1 -1
  14. data/bats/block-type-opts.bats +3 -3
  15. data/bats/block-type-port.bats +2 -2
  16. data/bats/block-type-shell-require-ux.bats +2 -2
  17. data/bats/block-type-ux-allowed.bats +4 -4
  18. data/bats/block-type-ux-auto.bats +1 -1
  19. data/bats/block-type-ux-chained.bats +1 -1
  20. data/bats/block-type-ux-default.bats +1 -1
  21. data/bats/block-type-ux-echo-hash-transform.bats +1 -1
  22. data/bats/block-type-ux-echo-hash.bats +2 -2
  23. data/bats/block-type-ux-echo.bats +3 -3
  24. data/bats/block-type-ux-exec-hash-transform.bats +1 -1
  25. data/bats/block-type-ux-exec-hash.bats +2 -2
  26. data/bats/block-type-ux-exec.bats +1 -1
  27. data/bats/block-type-ux-force.bats +1 -1
  28. data/bats/block-type-ux-formats.bats +1 -1
  29. data/bats/block-type-ux-hidden.bats +1 -1
  30. data/bats/block-type-ux-invalid.bats +1 -1
  31. data/bats/block-type-ux-readonly.bats +1 -1
  32. data/bats/block-type-ux-require-chained.bats +2 -2
  33. data/bats/block-type-ux-require-context.bats +2 -2
  34. data/bats/block-type-ux-require.bats +2 -2
  35. data/bats/block-type-ux-required-variables.bats +1 -1
  36. data/bats/block-type-ux-row-format.bats +1 -1
  37. data/bats/block-type-ux-sources.bats +4 -4
  38. data/bats/block-type-ux-transform.bats +1 -1
  39. data/bats/block-type-vars.bats +3 -3
  40. data/bats/border.bats +1 -1
  41. data/bats/cli.bats +11 -11
  42. data/bats/command-substitution-options.bats +2 -2
  43. data/bats/command-substitution.bats +1 -1
  44. data/bats/document-shell.bats +1 -1
  45. data/bats/history.bats +5 -5
  46. data/bats/import-conflict.bats +1 -1
  47. data/bats/import-directive-line-continuation.bats +1 -1
  48. data/bats/import-directive-parameter-symbols.bats +1 -1
  49. data/bats/import-duplicates.bats +6 -6
  50. data/bats/import-parameter-symbols.bats +1 -1
  51. data/bats/import-with-text-substitution.bats +1 -1
  52. data/bats/import.bats +3 -3
  53. data/bats/indented-block-type-vars.bats +1 -1
  54. data/bats/indented-multi-line-output.bats +1 -1
  55. data/bats/line-decor-dynamic.bats +1 -1
  56. data/bats/line-wrapping.bats +1 -1
  57. data/bats/load-vars-state-demo.bats +4 -4
  58. data/bats/markup.bats +4 -4
  59. data/bats/mde.bats +4 -4
  60. data/bats/option-expansion.bats +1 -1
  61. data/bats/options-collapse.bats +4 -4
  62. data/bats/options.bats +47 -17
  63. data/bats/plain.bats +1 -1
  64. data/bats/publish.bats +2 -2
  65. data/bats/table-column-truncate.bats +1 -1
  66. data/bats/table.bats +2 -2
  67. data/bats/variable-expansion-multiline.bats +1 -1
  68. data/bats/variable-expansion.bats +6 -6
  69. data/conversation-template.md +611 -0
  70. data/docs/block-execution-modes.md +177 -0
  71. data/docs/block-filtering.md +252 -0
  72. data/docs/block-naming-patterns.md +210 -0
  73. data/docs/block-scanning-patterns.md +248 -0
  74. data/docs/cli-reference.md +370 -0
  75. data/docs/dev/block-hide.md +1 -1
  76. data/docs/dev/block-type-ux-transform.md +5 -4
  77. data/docs/dev/print_bytes.md +3 -0
  78. data/docs/dev/shebang.md +6 -0
  79. data/docs/docker-testing.md +5 -0
  80. data/docs/execution-control.md +384 -0
  81. data/docs/getting-started.md +209 -0
  82. data/docs/import-options.md +391 -0
  83. data/docs/tab-completion.md +7 -0
  84. data/docs/ux-blocks.md +376 -0
  85. data/examples/linked1.md +8 -1
  86. data/implementation-decisions.md +212 -0
  87. data/lib/cached_nested_file_reader.rb +138 -1
  88. data/lib/command_result.rb +27 -6
  89. data/lib/executed_shell_command.rb +512 -0
  90. data/lib/filter.rb +7 -7
  91. data/lib/hash_delegator.rb +403 -350
  92. data/lib/link_history.rb +22 -11
  93. data/lib/markdown_exec/version.rb +1 -1
  94. data/lib/mdoc.rb +103 -44
  95. data/lib/menu.src.yml +110 -83
  96. data/lib/menu.yml +149 -83
  97. data/lib/transformed_shell_command.rb +449 -0
  98. data/lib/wl.rb +15 -0
  99. data/lib/ww.rb +16 -5
  100. data/requirements.md +111 -0
  101. data/semantic-tokens.md +132 -0
  102. data/tasks.md +69 -0
  103. metadata +26 -4
  104. data/docs/ux-blocks-examples.md +0 -120
  105. data/docs/ux-blocks-init-act.md +0 -100
@@ -27,6 +27,7 @@ require_relative 'constants'
27
27
  require_relative 'directory_searcher'
28
28
  require_relative 'error_reporting'
29
29
  require_relative 'evaluate_shell_expressions'
30
+ require_relative 'executed_shell_command'
30
31
  require_relative 'exceptions'
31
32
  require_relative 'fcb'
32
33
  require_relative 'filter'
@@ -43,12 +44,12 @@ require_relative 'streams_out'
43
44
  require_relative 'string_util'
44
45
  require_relative 'table_extractor'
45
46
  require_relative 'text_analyzer'
47
+ require_relative 'transformed_shell_command'
46
48
  require_relative 'value_or_exception'
47
49
  require_relative 'env_interface'
48
50
 
49
51
  $pd = false unless defined?($pd)
50
52
  $table_cell_truncate = true
51
- EXIT_STATUS_REQUIRED_EMPTY = 248
52
53
 
53
54
  module HashDelegatorSelf
54
55
  # Applies an ANSI color method to a string using a specified color key.
@@ -164,8 +165,7 @@ module HashDelegatorSelf
164
165
 
165
166
  # delete the current line if it is empty and the previous is also empty
166
167
  def delete_consecutive_blank_lines!(blocks_menu)
167
- blocks_menu.process_and_conditionally_delete! do
168
- |prev_item, current_item, _next_item|
168
+ blocks_menu.process_and_conditionally_delete! do |prev_item, current_item, _next_item|
169
169
  !current_item.is_split? &&
170
170
  prev_item&.fetch(:chrome, nil) &&
171
171
  !(prev_item && prev_item.oname.present?) &&
@@ -181,6 +181,125 @@ module HashDelegatorSelf
181
181
  )
182
182
  end
183
183
 
184
+ # Executes a bash script from an array of lines and optionally exports the result.
185
+ #
186
+ # Creates a temporary executable file from the provided bash script lines,
187
+ # executes it using the configured shell, and optionally archives the script
188
+ # if archiving is enabled. The result can be marked as exportable based on
189
+ # the export configuration and command success status.
190
+ #
191
+ # @param bash_script_lines [Array<String>] An array of strings representing
192
+ # the lines of the bash script to execute.
193
+ # @param export [Object, nil] An export configuration object that determines
194
+ # whether the result should be exported. If provided, must respond to:
195
+ # - `exportable` [Boolean, nil] Whether the result is exportable (nil defaults to true)
196
+ # - `name` [String] The name of the variable to export the result to
197
+ # If nil, the result will not be exported.
198
+ # @param force [Boolean] Whether to force the export even if the variable
199
+ # already exists. This is included in the new_lines hash if exportable.
200
+ #
201
+ # @return [Array, nil] Returns an array with three elements:
202
+ # - [0] [CommandResult] The result of executing the script, containing
203
+ # stdout and exit_status
204
+ # - [1] [Boolean] Whether the result is exportable (true if export is nil
205
+ # or export.exportable is nil, otherwise based on export.exportable and
206
+ # command success)
207
+ # - [2] [Array<Hash>] An array of hashes representing new lines to export.
208
+ # Each hash contains:
209
+ # - `:name` [String] The variable name
210
+ # - `:force` [Boolean] Whether to force the export
211
+ # - `:text` [String] The text content to export
212
+ # Returns nil if an error occurs during execution.
213
+ #
214
+ # @note The method creates a temporary file with mode 0755 (executable) and
215
+ # automatically cleans it up after execution. If `@delegate_object[:archive_ad_hoc_scripts]`
216
+ # is true, the script will be copied to an archive location before execution.
217
+ #
218
+ # @example Basic execution without export
219
+ # result, exportable, new_lines = execute_bash_script_lines(
220
+ # code_lines: ["echo 'Hello'", "echo 'World'"],
221
+ # export: nil,
222
+ # force: false
223
+ # )
224
+ # # => [CommandResult, true, []]
225
+ #
226
+ # @example Execution with export
227
+ # export = OpenStruct.new(name: "MY_VAR", exportable: true)
228
+ # result, exportable, new_lines = execute_bash_script_lines(
229
+ # code_lines: ["echo 'Hello World'"],
230
+ # export: export,
231
+ # force: true
232
+ # )
233
+ # # => [CommandResult, true, [{name: "MY_VAR", force: true, text: "Hello World\n"}]]
234
+ #
235
+ def execute_bash_script_lines(
236
+ code_lines: [],
237
+ export: nil,
238
+ export_name:,
239
+ force: false,
240
+ shell:,
241
+ archive_script: false, # @delegate_object[:archive_ad_hoc_scripts]
242
+ archive_path_format: nil, # @delegate_object[:archive_path_format],
243
+ archive_time_format: nil # @delegate_object[:archive_time_format]
244
+ )
245
+ Tempfile.create('script_exec') do |temp_file|
246
+ temp_file.write(join_code_lines(code_lines))
247
+ temp_file.close # Close the file before chmod and execution
248
+ File.chmod(0o755, temp_file.path)
249
+
250
+ if archive_script && archive_path_format && archive_time_format
251
+ archive_filename = format(
252
+ archive_path_format,
253
+ time: Time.now.strftime(archive_time_format)
254
+ )
255
+ `cp #{temp_file.path} #{archive_filename}`
256
+ end
257
+
258
+ tsc = TransformedShellCommand.new(
259
+ "#{shell} #{temp_file.path}",
260
+ regex: export.validate,
261
+ format: export.transform
262
+ )
263
+ exportable = if export&.exportable.nil?
264
+ true
265
+ else
266
+ (export ? export.exportable : false)
267
+ end
268
+ new_lines = []
269
+
270
+ if false
271
+ # optional comment
272
+ new_lines << { comment: 'execute_bash_script_lines' }
273
+ end
274
+
275
+ exportable_success = exportable && tsc.success?
276
+ if exportable_success
277
+ new_lines << { name: export_name, force: force,
278
+ text: tsc.transformed_output }
279
+ end
280
+
281
+ CommandResult.new(
282
+ exit_status: tsc.exit_code,
283
+ exportable: exportable_success,
284
+ new_lines: new_lines,
285
+ script: code_lines,
286
+ stdout: tsc.transformed_output,
287
+ transformed_shell_command: tsc
288
+ )
289
+ end
290
+ rescue StandardError
291
+ wwe $!, 'code_lines:', code_lines, 'export:', export
292
+ # warn "Error executing script: #{err.message}"
293
+ # return failure result
294
+ CommandResult.new(
295
+ exit_status: CommandResult::EXIT_STATUS_FAIL,
296
+ exportable: false,
297
+ new_lines: [],
298
+ script: code_lines,
299
+ stdout: ''
300
+ )
301
+ end
302
+
184
303
  # Takes multiple arrays as arguments, flattens them into a single array, and removes nil values.
185
304
  # @param args [Array<Array>] Variable number of arrays to be processed
186
305
  # @return [Array] A single flattened array with nil values removed, or an empty array if the result is empty
@@ -207,6 +326,8 @@ module HashDelegatorSelf
207
326
 
208
327
  def join_code_lines(lines)
209
328
  ((lines || []) + ['']).join("\n")
329
+ rescue StandardError
330
+ wwe $!, 'lines:', lines
210
331
  end
211
332
 
212
333
  def next_link_state(
@@ -448,14 +569,14 @@ module HashDelegatorSelf
448
569
 
449
570
  # if the id is present, update the existing fcb
450
571
  if options[:id]
451
- fcb = all_fcbs.find { |fcb| fcb.id == options[:id] }
572
+ fcb = all_fcbs.find { _1.id == options[:id] }
452
573
  if fcb
453
574
  fcb.update(options)
454
575
  return fcb
455
576
  end
456
577
  end
457
- MarkdownExec::FCB.new(options).tap do |fcb|
458
- all_fcbs << fcb
578
+ MarkdownExec::FCB.new(options).tap do
579
+ all_fcbs << _1
459
580
  end
460
581
  end
461
582
  end
@@ -656,7 +777,7 @@ module MarkdownExec
656
777
  source: OpenStruct.new
657
778
  )
658
779
  @link_history = LinkHistory.new
659
- @fout = FOut.new(@delegate_object) ### slice only relevant keys
780
+ @fout = FOut.new(@delegate_object)
660
781
 
661
782
  @process_mutex = Mutex.new
662
783
  @process_cv = ConditionVariable.new
@@ -883,7 +1004,6 @@ module MarkdownExec
883
1004
  # @param block_type_color_option [Symbol, nil] The shell color option to apply.
884
1005
  # @return [String] The colorized or original name string.
885
1006
  def apply_block_type_color_option(name, block_type_color_option)
886
- ### accept string for color
887
1007
  if block_type_color_option && @delegate_object[block_type_color_option].present?
888
1008
  string_send_color(name, block_type_color_option)
889
1009
  else
@@ -1123,7 +1243,9 @@ module MarkdownExec
1123
1243
  symbol_raw_literal:
1124
1244
  @delegate_object[:import_symbol_raw_literal],
1125
1245
  symbol_variable_reference:
1126
- @delegate_object[:import_symbol_variable_reference]
1246
+ @delegate_object[:import_symbol_variable_reference],
1247
+ hide_shebang:
1248
+ @delegate_object.fetch(:hide_shebang, true)
1127
1249
  )
1128
1250
  end
1129
1251
 
@@ -1142,7 +1264,9 @@ module MarkdownExec
1142
1264
  end
1143
1265
 
1144
1266
  # return code resulting from evaluating all automatic blocks in order
1145
- def code_from_auto_blocks(all_blocks, mdoc: nil, default_only: true)
1267
+ def code_from_auto_blocks(
1268
+ all_blocks, mdoc: nil, default_only: true, inherited_lines: []
1269
+ )
1146
1270
  shell_block_name = @delegate_object[:document_load_shell_block_name]
1147
1271
  vars_block_name = @delegate_object[:document_load_vars_block_name]
1148
1272
 
@@ -1155,21 +1279,26 @@ module MarkdownExec
1155
1279
  @vars_most_recent_filename = @delegate_object[:filename]
1156
1280
 
1157
1281
  all_code = []
1282
+ all_inherit = []
1158
1283
  all_blocks.each do |fcb|
1159
1284
  block_code = []
1285
+ block_inherit = []
1160
1286
  if fcb.oname == shell_block_name
1161
1287
  if read_shell
1162
1288
  # collect code from shell block
1163
1289
  code = if mdoc
1164
- mdoc.collect_recursively_required_code(
1290
+ reqinfo = mdoc.collect_recursively_required_code(
1165
1291
  anyname: fcb.id,
1166
- ### anyname: fcb.pub_name,
1167
1292
  label_format_above:
1168
1293
  @delegate_object[:shell_code_label_format_above],
1169
1294
  label_format_below:
1170
1295
  @delegate_object[:shell_code_label_format_below],
1171
- block_source: block_source
1172
- )[:code]
1296
+ block_source: block_source,
1297
+ inherited_lines: inherited_lines
1298
+ )
1299
+ block_inherit = reqinfo[:inherit]
1300
+
1301
+ reqinfo[:code]
1173
1302
  else
1174
1303
  fcb.body
1175
1304
  end
@@ -1207,43 +1336,12 @@ module MarkdownExec
1207
1336
 
1208
1337
  wwt :code, 'block_code:', block_code unless block_code&.empty?
1209
1338
  all_code += block_code
1339
+ all_inherit += block_inherit
1210
1340
  end
1211
1341
 
1212
- all_code.tap { wwt :code, _1 }
1342
+ [all_code, all_inherit].tap { wwr _1 }
1213
1343
  rescue StandardError
1214
- wwe 'all_blocks.count:', all_blocks.count
1215
- end
1216
-
1217
- # return code resulting from evaluating all automatic SHELL blocks in order
1218
- def code_from_auto_shell_blocks(all_blocks, mdoc: nil)
1219
- # a block name is required
1220
- # do not reload the most recent filename
1221
- block_name = @delegate_object[:document_load_shell_block_name]
1222
- unless block_name.present? &&
1223
- @shell_most_recent_filename != @delegate_object[:filename]
1224
- return
1225
- end
1226
-
1227
- @shell_most_recent_filename = @delegate_object[:filename]
1228
-
1229
- # select the first block with the given name
1230
- fcb = HashDelegator.block_find(all_blocks, :oname, block_name)
1231
- return unless fcb
1232
-
1233
- # collect code from shell block
1234
- code = if mdoc
1235
- mdoc.collect_recursively_required_code(
1236
- anyname: fcb.pub_name,
1237
- label_format_above:
1238
- @delegate_object[:shell_code_label_format_above],
1239
- label_format_below:
1240
- @delegate_object[:shell_code_label_format_below],
1241
- block_source: block_source
1242
- )[:code]
1243
- else
1244
- fcb.body
1245
- end
1246
- annotate_required_lines('blk:SHELL', code, block_name: fcb.id)
1344
+ wwe $!, 'all_blocks.count:', all_blocks.count
1247
1345
  end
1248
1346
 
1249
1347
  # return code resulting from evaluating all UX blocks in order
@@ -1321,6 +1419,7 @@ module MarkdownExec
1321
1419
  required: nil, silent:
1322
1420
  )
1323
1421
  wwt :fcb, 'selected:', selected
1422
+ wwt :inherited_code, inherited_code
1324
1423
  ret_command_result = nil
1325
1424
  exit_prompt = @delegate_object[:prompt_filespec_back]
1326
1425
 
@@ -1328,14 +1427,16 @@ module MarkdownExec
1328
1427
  anyname: selected_id_name(selected),
1329
1428
  label_format_above: @delegate_object[:shell_code_label_format_above],
1330
1429
  label_format_below: @delegate_object[:shell_code_label_format_below],
1331
- block_source: block_source
1430
+ block_source: block_source,
1431
+ inherited_lines: inherited_code
1332
1432
  )
1333
1433
  wwt :required, required
1434
+ # new_inherited_lines = []
1334
1435
 
1335
1436
  # process each ux block in sequence, setting ENV and collecting lines
1336
1437
  concatenated_code_from_required_blocks = []
1337
1438
  required[:blocks]&.each do |block|
1338
- next unless block.type == BlockType::UX
1439
+ next if block.type != BlockType::UX
1339
1440
 
1340
1441
  wwt :fcb, 'a required block', block
1341
1442
 
@@ -1355,7 +1456,7 @@ module MarkdownExec
1355
1456
  # if any precondition is not set, the sequence is aborted.
1356
1457
  required_variables = []
1357
1458
  export.required&.each do |precondition|
1358
- required_variables.push "[[ -z $#{precondition} ]] && exit #{EXIT_STATUS_REQUIRED_EMPTY}"
1459
+ required_variables.push "[[ -z $#{precondition} ]] && exit #{CommandResult::EXIT_STATUS_REQUIRED_EMPTY}"
1359
1460
  end
1360
1461
  wwt :required_variables, 'required_variables',
1361
1462
  required_variables
@@ -1401,7 +1502,8 @@ module MarkdownExec
1401
1502
  raise "Invalid data type: #{data.inspect}"
1402
1503
  end
1403
1504
  end
1404
- wwt :concatenated_code_from_required_blocks, concatenated_code_from_required_blocks
1505
+ wwt :concatenated_code_from_required_blocks,
1506
+ concatenated_code_from_required_blocks
1405
1507
 
1406
1508
  (ret_command_result || CommandResult.new(
1407
1509
  stdout: annotate_required_lines(
@@ -1411,7 +1513,7 @@ module MarkdownExec
1411
1513
  wwt :cr, ret, caller.deref
1412
1514
  end
1413
1515
  rescue StandardError
1414
- wwe 'selected:', selected, 'required:', required, 'block:', block,
1516
+ wwe $!, 'selected:', selected, 'required:', required, 'block:', block,
1415
1517
  'data:', data
1416
1518
  end
1417
1519
 
@@ -1887,16 +1989,7 @@ module MarkdownExec
1887
1989
  case_conversion: criteria[:case_conversion],
1888
1990
  center: criteria[:center] &&
1889
1991
  @delegate_object[criteria[:center]],
1890
-
1891
- collapse: case fcb.collapse_token
1892
- when COLLAPSIBLE_TOKEN_COLLAPSE
1893
- true
1894
- when COLLAPSIBLE_TOKEN_EXPAND
1895
- false
1896
- else
1897
- false
1898
- end,
1899
-
1992
+ collapse: fcb.collapse_token == COLLAPSIBLE_TOKEN_COLLAPSE,
1900
1993
  color_method: criteria[:color] &&
1901
1994
  @delegate_object[criteria[:color]].to_sym,
1902
1995
  decor_patterns:
@@ -2278,7 +2371,7 @@ module MarkdownExec
2278
2371
  link_state: link_state,
2279
2372
  block_source: block_source
2280
2373
  )
2281
- next_state_set_code(selected, link_state, required_lines)
2374
+ next_state_append_code(selected, link_state, required_lines)
2282
2375
 
2283
2376
  elsif selected.type == BlockType::UX
2284
2377
  debounce_reset
@@ -2289,7 +2382,6 @@ module MarkdownExec
2289
2382
  only_default: false,
2290
2383
  silent: true
2291
2384
  )
2292
- ### TBD if command_result_w_e_t_nl.failure?
2293
2385
  next_state_append_code(
2294
2386
  selected,
2295
2387
  link_state,
@@ -2310,6 +2402,7 @@ module MarkdownExec
2310
2402
  LoadFileLinkState.new(LoadFile::REUSE, link_state)
2311
2403
 
2312
2404
  elsif debounce_allows
2405
+ # SHELL type
2313
2406
  compile_execute_and_trigger_reuse(mdoc: mdoc,
2314
2407
  selected: selected,
2315
2408
  link_state: link_state,
@@ -2412,7 +2505,8 @@ module MarkdownExec
2412
2505
  anyname: selected_id_name(selected),
2413
2506
  block_source: block_source,
2414
2507
  label_format_above: @delegate_object[:shell_code_label_format_above],
2415
- label_format_below: @delegate_object[:shell_code_label_format_below]
2508
+ label_format_below: @delegate_object[:shell_code_label_format_below],
2509
+ inherited_lines: link_state&.inherited_lines || []
2416
2510
  )
2417
2511
  code_lines = annotate_required_lines(
2418
2512
  'blk:LINK', code_info[:code], block_name: selected.id
@@ -2593,7 +2687,8 @@ module MarkdownExec
2593
2687
  anyname: selected_id_name(selected),
2594
2688
  label_format_above: @delegate_object[:shell_code_label_format_above],
2595
2689
  label_format_below: @delegate_object[:shell_code_label_format_below],
2596
- block_source: block_source
2690
+ block_source: block_source,
2691
+ inherited_lines: link_state&.inherited_lines || []
2597
2692
  ) # !!t 'required'
2598
2693
  dependencies = (
2599
2694
  link_state&.inherited_dependencies || {}
@@ -2627,10 +2722,10 @@ module MarkdownExec
2627
2722
  else
2628
2723
  # code from the selected VARS block
2629
2724
  vars_code = if selected.type == BlockType::VARS
2630
- code_from_vars_block_to_set_environment_variables(selected)
2631
- else
2632
- []
2633
- end
2725
+ code_from_vars_block_to_set_environment_variables(selected)
2726
+ else
2727
+ []
2728
+ end
2634
2729
 
2635
2730
  # activate UX blocks in the required list
2636
2731
  command_result_w_e_t_nl = code_from_ux_block_to_set_environment_variables(
@@ -2652,7 +2747,6 @@ module MarkdownExec
2652
2747
  )
2653
2748
  )
2654
2749
  end
2655
-
2656
2750
  rescue StandardError
2657
2751
  wwe(required[:code], ux_code, { error: $!, callback: $@[0] })
2658
2752
  end
@@ -2926,7 +3020,8 @@ module MarkdownExec
2926
3020
  end
2927
3021
 
2928
3022
  def ux_block_eval_for_export(
2929
- bash_script_lines, export,
3023
+ bash_script_lines,
3024
+ export,
2930
3025
  data:,
2931
3026
  first_only: false,
2932
3027
  force:,
@@ -2934,68 +3029,90 @@ module MarkdownExec
2934
3029
  silent:,
2935
3030
  string: nil
2936
3031
  )
2937
- exportable = true
2938
3032
  command_result = nil
2939
3033
  new_lines = []
3034
+ required_lines = []
2940
3035
  export_string = string.nil? ? data : string
2941
3036
  expander = ->(expression) { %(printf '%s' "#{expression}") }
3037
+ exportable = true
2942
3038
 
2943
3039
  case export_string
2944
3040
  when String, Integer, Float, TrueClass, FalseClass
2945
- command_result, exportable, = output_from_adhoc_bash_script_file(
2946
- join_array_of_arrays(
3041
+ command_result = HashDelegator.execute_bash_script_lines(
3042
+ code_lines: join_array_of_arrays(
2947
3043
  bash_script_lines,
2948
3044
  printf_expand ? expander.call(export_string) : [export_string]
2949
3045
  ),
2950
- export,
2951
- force: force
3046
+ export: export,
3047
+ export_name: export.name,
3048
+ force: force,
3049
+ shell: @delegate_object[:shell],
3050
+ archive_script: @delegate_object[:archive_ad_hoc_scripts],
3051
+ archive_path_format: @delegate_object[:archive_path_format],
3052
+ archive_time_format: @delegate_object[:archive_time_format]
2952
3053
  )
2953
- if command_result.exit_status == EXIT_STATUS_REQUIRED_EMPTY
3054
+ exportable = command_result&.exportable
3055
+
3056
+ if command_result&.exit_status == CommandResult::EXIT_STATUS_REQUIRED_EMPTY
2954
3057
  exportable = false
2955
3058
  unless silent
2956
3059
  command_result.warning = warning_required_empty(export)
2957
3060
  end
3061
+ elsif command_result.nil? || command_result.failure?
3062
+ exportable = false
3063
+ unless silent
3064
+ command_result.warning = warning_failure(export)
3065
+ end
2958
3066
  else
2959
- # store the transformed value in ENV
2960
- EnvInterface.set(export.name, command_result.stdout.to_s)
2961
-
2962
- new_lines << { name: export.name, force: force,
2963
- text: command_result.stdout }
3067
+ out = command_result.stdout.to_s
3068
+ if export_environment_variable(export.name, out)
3069
+ new_lines << {
3070
+ name: export.name, force: force, text: out
3071
+ }
3072
+ required_lines << code_line_to_assign_a_variable(
3073
+ export.name, out, force: force
3074
+ )
3075
+ end
2964
3076
  end
2965
3077
 
2966
3078
  when Hash
2967
- required_lines = []
2968
3079
 
2969
3080
  # each item in the hash is a variable name and value
2970
3081
  export_string.each do |name, expression|
2971
- command_result, = output_from_adhoc_bash_script_file(
2972
- join_array_of_arrays(
3082
+ command_result = HashDelegator.execute_bash_script_lines(
3083
+ code_lines: join_array_of_arrays(
2973
3084
  bash_script_lines,
2974
3085
  required_lines,
2975
3086
  printf_expand ? expander.call(expression) : [expression]
2976
3087
  ),
2977
- export,
2978
- force: force
2979
- )
2980
- if command_result.exit_status == EXIT_STATUS_REQUIRED_EMPTY
3088
+ export: export,
3089
+ export_name: name,
3090
+ force: force,
3091
+ shell: @delegate_object[:shell],
3092
+ archive_script: @delegate_object[:archive_ad_hoc_scripts],
3093
+ archive_path_format: @delegate_object[:archive_path_format],
3094
+ archive_time_format: @delegate_object[:archive_time_format]
3095
+ ).tap { ww _1 }
3096
+ if command_result.exit_status == CommandResult::EXIT_STATUS_REQUIRED_EMPTY
3097
+ exportable = false
2981
3098
  unless silent
2982
3099
  command_result.warning = warning_required_empty(export)
2983
3100
  end
3101
+ elsif command_result.failure?
3102
+ exportable = false
3103
+ unless silent
3104
+ command_result.warning = warning_failure(export)
3105
+ end
2984
3106
  else
2985
- transformed = command_result.stdout.to_s
2986
-
2987
- # code for subsequent expression evaluations
3107
+ out = command_result.stdout.to_s
3108
+ if export_environment_variable(name, out)
3109
+ new_lines << {
3110
+ name: name, force: force, text: out
3111
+ }
3112
+ end
2988
3113
  required_lines << code_line_to_assign_a_variable(
2989
- name, transformed, force: force
3114
+ name, out, force: force
2990
3115
  )
2991
-
2992
- if variable_is_exportable(name)
2993
- # store the transformed value in ENV
2994
- EnvInterface.set(name, transformed)
2995
-
2996
- new_lines << { name: name, force: force,
2997
- text: command_result.stdout }
2998
- end
2999
3116
  end
3000
3117
 
3001
3118
  break if first_only
@@ -3004,9 +3121,23 @@ module MarkdownExec
3004
3121
  # do nothing
3005
3122
  end
3006
3123
 
3007
- [command_result, exportable, new_lines]
3124
+ if command_result
3125
+ command_result.exportable = exportable
3126
+ command_result.new_lines = new_lines
3127
+ command_result.required_lines = required_lines
3128
+ end
3129
+
3130
+ command_result
3008
3131
  rescue StandardError
3009
- wwe bash_script_lines, export, force, silent, string
3132
+ wwe $!, bash_script_lines, export, force, silent, string
3133
+ end
3134
+
3135
+ def export_environment_variable(export_name, stdout)
3136
+ return unless variable_is_exportable(export_name)
3137
+
3138
+ EnvInterface.set(export_name, stdout)
3139
+
3140
+ true
3010
3141
  end
3011
3142
 
3012
3143
  # Retrieves a specific data symbol from the delegate object,
@@ -3702,18 +3833,24 @@ module MarkdownExec
3702
3833
  reload_blocks = true
3703
3834
  end
3704
3835
 
3836
+ link_state_inherited_lines = link_state&.inherited_lines || []
3837
+
3705
3838
  # return code resulting from evaluating all SHELL, UX, VARS blocks;
3706
3839
  # each set in sequence; with its own order
3707
3840
  #
3708
- if (code_lines = code_from_auto_blocks(
3841
+ if (code_lines, _inherit_lines = code_from_auto_blocks(
3709
3842
  all_blocks,
3710
3843
  default_only: true,
3711
- mdoc: mdoc
3844
+ mdoc: mdoc,
3845
+ inherited_lines: link_state_inherited_lines
3712
3846
  ))&.select_by(:empty?, false)&.compact&.count&.positive?
3713
- new_code = code_lines
3714
3847
  wwt :code_lines, 'code_lines:', code_lines
3848
+
3849
+ # prepend inherited lines to new code lines
3850
+ new_code = link_state_inherited_lines + code_lines
3715
3851
  next_state_set_code(nil, link_state, new_code)
3716
3852
  link_state.inherited_lines = new_code
3853
+
3717
3854
  reload_blocks = true
3718
3855
  else
3719
3856
  link_state&.inherited_lines
@@ -3876,6 +4013,14 @@ module MarkdownExec
3876
4013
  end
3877
4014
  end
3878
4015
 
4016
+ # Define respond_to_missing? to match method_missing behavior
4017
+ def respond_to_missing?(method_name, include_private = false)
4018
+ return true if @delegate_object.respond_to?(method_name, include_private)
4019
+ return true if method_name.to_s.end_with?('=')
4020
+
4021
+ true # Any other method name is treated as a key accessor
4022
+ end
4023
+
3879
4024
  # If a method is missing, treat it as a key for the @delegate_object.
3880
4025
  def method_missing(method_name, *args, &block)
3881
4026
  if @delegate_object.respond_to?(method_name)
@@ -3961,60 +4106,6 @@ module MarkdownExec
3961
4106
  }
3962
4107
  end
3963
4108
 
3964
- def output_from_adhoc_bash_script_file(
3965
- bash_script_lines,
3966
- export = nil,
3967
- force:
3968
- )
3969
- Tempfile.create('script_exec') do |temp_file|
3970
- temp_file.write(
3971
- HashDelegator.join_code_lines(
3972
- bash_script_lines
3973
- )
3974
- )
3975
- temp_file.close # Close the file before chmod and execution
3976
- File.chmod(0o755, temp_file.path)
3977
-
3978
- if @delegate_object[:archive_ad_hoc_scripts]
3979
- archive_filename = format(
3980
- @delegate_object[:archive_path_format],
3981
- time: Time.now.strftime(@delegate_object[:archive_time_format])
3982
- )
3983
- `cp #{temp_file.path} #{archive_filename}`
3984
- end
3985
-
3986
- output = `#{shell} #{temp_file.path}`
3987
-
3988
- exportable = if export&.exportable.nil?
3989
- true
3990
- else
3991
- (export ? export.exportable : false)
3992
- end
3993
- new_lines = []
3994
- # new_lines << { comment: 'output_from_adhoc_bash_script_file' }
3995
-
3996
- command_result = CommandResult.new(
3997
- stdout: output,
3998
- exit_status: $?.exitstatus
3999
- )
4000
- exportable &&= command_result.success?
4001
- if exportable
4002
- new_lines << { name: export.name, force: force,
4003
- text: command_result.stdout }
4004
- end
4005
-
4006
- [
4007
- command_result,
4008
- exportable,
4009
- new_lines
4010
- ]
4011
- end
4012
- rescue StandardError => err
4013
- # wwe 'bash_script_lines:', bash_script_lines, 'export:', export
4014
- warn "Error executing script: #{err.message}"
4015
- nil
4016
- end
4017
-
4018
4109
  def output_labeled_value(label, value, level)
4019
4110
  @fout.lout format_references_send_color(
4020
4111
  context: {
@@ -4108,7 +4199,7 @@ module MarkdownExec
4108
4199
  blocks.select { |item| item.type == 'ux' }
4109
4200
  end
4110
4201
 
4111
- # Filter blocks per block_name_include_match, block_name_wrapper_match.
4202
+ # Filter blocks per block_name_hidden_match, block_name_wrapper_match.
4112
4203
  #
4113
4204
  # @param all_blocks [Array<Hash>] The list of blocks from the file.
4114
4205
  def select_blocks(menu_blocks)
@@ -4116,12 +4207,12 @@ module MarkdownExec
4116
4207
  Filter.prepared_not_in_menu?(
4117
4208
  @delegate_object,
4118
4209
  fcb,
4119
- %i[block_name_include_match block_name_wrapper_match]
4210
+ %i[block_name_hidden_match block_name_wrapper_match]
4120
4211
  )
4121
4212
  end
4122
4213
  end
4123
4214
 
4124
- # Filter blocks per block_name_include_match, block_name_wrapper_match.
4215
+ # Filter blocks per block_name_hidden_match, block_name_wrapper_match.
4125
4216
  # Set name displayed by tty-prompt.
4126
4217
  #
4127
4218
  # @param all_blocks [Array<Hash>] The list of blocks from the file.
@@ -4167,11 +4258,7 @@ module MarkdownExec
4167
4258
  if name.empty?
4168
4259
  "# #{comment}" unless comment.empty?
4169
4260
  else
4170
- transformed = if command_result_w_e_t_nl.transformable
4171
- transform_export_value(name_force[:text], export)
4172
- else
4173
- name_force[:text]
4174
- end
4261
+ transformed = name_force[:text]
4175
4262
 
4176
4263
  # store the transformed value in ENV
4177
4264
  EnvInterface.set(name, transformed)
@@ -4592,7 +4679,7 @@ module MarkdownExec
4592
4679
  @delegate_object.respond_to?(:[]=, include_private)
4593
4680
  true
4594
4681
  else
4595
- @delegate_object.respond_to?(method_name, include_private)
4682
+ false
4596
4683
  end
4597
4684
  end
4598
4685
 
@@ -4678,7 +4765,7 @@ module MarkdownExec
4678
4765
  saved_asset_format:
4679
4766
  shell_escape_asset_format(
4680
4767
  code_lines: link_state&.inherited_lines,
4681
- shell: selected_shell(shell)
4768
+ shell: selected_shell(@delegate_object[:shell])
4682
4769
  )
4683
4770
  ).generate_name
4684
4771
  end
@@ -4966,7 +5053,6 @@ module MarkdownExec
4966
5053
  # color_sym is not found in @delegate_object.
4967
5054
  # @return [String] The string with the applied color method.
4968
5055
  def string_send_color(string, color_sym)
4969
- ### accept string with color as well as symbol for color_hash
4970
5056
  HashDelegator.apply_color_from_hash(string, @delegate_object, color_sym)
4971
5057
  end
4972
5058
 
@@ -5094,21 +5180,20 @@ module MarkdownExec
5094
5180
  force = export.force.nil? ? true : export.force
5095
5181
  new_lines = []
5096
5182
  silent = false
5097
- transformable = true
5098
5183
 
5099
5184
  case FCB.act_source(export)
5100
5185
  when false, UxActSource::FALSE
5101
5186
  # read-only
5102
- command_result = CommandResult.new
5103
- exportable = false
5104
- transformable = false
5187
+ command_result = CommandResult.new(
5188
+ exportable: false
5189
+ )
5105
5190
 
5106
5191
  when :allow, UxActSource::ALLOW
5107
5192
  raise unless export.allow.present?
5108
5193
 
5109
5194
  case export.allow
5110
5195
  when :echo, ExportValueSource::ECHO
5111
- command_result, exportable, new_lines = ux_block_eval_for_export(
5196
+ command_result = ux_block_eval_for_export(
5112
5197
  bash_script_lines,
5113
5198
  export,
5114
5199
  data: export.echo,
@@ -5118,18 +5203,15 @@ module MarkdownExec
5118
5203
  silent: silent
5119
5204
  )
5120
5205
 
5121
- if command_result.failure?
5122
- command_result
5123
- else
5124
- command_result = CommandResult.new(
5125
- stdout: menu_from_list_with_back(
5126
- command_result.stdout.split("\n")
5127
- )
5206
+ if command_result.success?
5207
+ command_result.exportable = true
5208
+ command_result.stdout = menu_from_list_with_back(
5209
+ com_exp_cod.command_result.stdout.split("\n")
5128
5210
  )
5129
5211
  end
5130
5212
 
5131
5213
  when :exec, UxActSource::EXEC
5132
- command_result, exportable, new_lines = ux_block_eval_for_export(
5214
+ command_result = ux_block_eval_for_export(
5133
5215
  bash_script_lines,
5134
5216
  export,
5135
5217
  data: export.exec,
@@ -5138,19 +5220,19 @@ module MarkdownExec
5138
5220
  silent: silent
5139
5221
  )
5140
5222
 
5141
- if command_result.exit_status == EXIT_STATUS_REQUIRED_EMPTY
5223
+ if command_result.exit_status ==
5224
+ CommandResult::EXIT_STATUS_REQUIRED_EMPTY
5142
5225
  command_result
5143
5226
  else
5144
- command_result = CommandResult.new(
5145
- stdout: menu_from_list_with_back(
5146
- command_result.stdout.split("\n")
5147
- )
5227
+ command_result.exportable = true
5228
+ command_result.stdout = menu_from_list_with_back(
5229
+ command_result.stdout.split("\n")
5148
5230
  )
5149
5231
  end
5150
5232
 
5151
5233
  else
5152
5234
  export_init = menu_from_list_with_back(export.allow)
5153
- command_result, exportable, new_lines = ux_block_eval_for_export(
5235
+ command_result = ux_block_eval_for_export(
5154
5236
  [assign_key_value_in_bash(export.name, export_init)],
5155
5237
  export,
5156
5238
  data: export.echo,
@@ -5160,11 +5242,13 @@ module MarkdownExec
5160
5242
  silent: silent,
5161
5243
  string: export_init
5162
5244
  )
5163
-
5245
+ if command_result.success?
5246
+ command_result.exportable = true
5247
+ end
5164
5248
  end
5165
5249
 
5166
5250
  when :echo, UxActSource::ECHO
5167
- command_result, exportable, new_lines = ux_block_eval_for_export(
5251
+ command_result = ux_block_eval_for_export(
5168
5252
  bash_script_lines,
5169
5253
  export,
5170
5254
  data: export.echo,
@@ -5172,6 +5256,9 @@ module MarkdownExec
5172
5256
  printf_expand: true,
5173
5257
  silent: silent
5174
5258
  )
5259
+ if command_result.success?
5260
+ command_result.exportable = true
5261
+ end
5175
5262
 
5176
5263
  when :edit, UxActSource::EDIT
5177
5264
  output = ''
@@ -5188,7 +5275,6 @@ module MarkdownExec
5188
5275
  end
5189
5276
  rescue Interrupt
5190
5277
  exportable = false
5191
- transformable = false
5192
5278
  end
5193
5279
 
5194
5280
  if exportable
@@ -5197,29 +5283,35 @@ module MarkdownExec
5197
5283
  text: output }
5198
5284
  end
5199
5285
  command_result = CommandResult.new(stdout: output)
5286
+ if command_result.success?
5287
+ command_result.exportable = true
5288
+ end
5200
5289
 
5201
5290
  when :exec, UxActSource::EXEC
5202
- command_result, exportable, new_lines = ux_block_eval_for_export(
5291
+ command_result = ux_block_eval_for_export(
5203
5292
  bash_script_lines,
5204
5293
  export,
5205
5294
  data: export.exec,
5206
5295
  force: force,
5207
5296
  silent: silent
5208
5297
  )
5298
+ if command_result.success?
5299
+ command_result.exportable = true
5300
+ end
5209
5301
 
5210
5302
  else
5211
- transformable = false
5212
5303
  command_result = CommandResult.new(stdout: export.default.to_s)
5304
+ if command_result.success?
5305
+ command_result.exportable = true
5306
+ end
5213
5307
  end
5214
5308
 
5215
5309
  # add message for required variables
5216
- if command_result.exit_status == EXIT_STATUS_REQUIRED_EMPTY
5310
+ if command_result.exit_status == CommandResult::EXIT_STATUS_REQUIRED_EMPTY
5217
5311
  command_result.warning = warning_required_empty(export)
5218
5312
  warn command_result.warning unless silent
5219
5313
  end
5220
5314
 
5221
- command_result.exportable = exportable
5222
- command_result.transformable = transformable
5223
5315
  command_result.new_lines = new_lines
5224
5316
  command_result
5225
5317
  rescue StandardError
@@ -5232,22 +5324,21 @@ module MarkdownExec
5232
5324
  command_result = nil
5233
5325
  exportable = true
5234
5326
  force = export.force.nil? ? false : export.force
5235
- new_lines = []
5236
5327
  silent = true
5237
- transformable = true
5238
5328
 
5239
5329
  case FCB.init_source(export)
5240
5330
  when false, UxActSource::FALSE
5241
5331
  exportable = false
5242
- transformable = false
5243
- command_result = CommandResult.new
5332
+ command_result = CommandResult.new(
5333
+ exportable: false
5334
+ )
5244
5335
 
5245
5336
  when :allow, UxActSource::ALLOW
5246
5337
  raise unless export.allow.present?
5247
5338
 
5248
5339
  case export.allow
5249
5340
  when :echo, ExportValueSource::ECHO
5250
- cr_echo, = ux_block_eval_for_export(
5341
+ cr_echo = ux_block_eval_for_export(
5251
5342
  bash_script_lines,
5252
5343
  export,
5253
5344
  data: export.echo,
@@ -5258,7 +5349,7 @@ module MarkdownExec
5258
5349
  )
5259
5350
  export_init = cr_echo.stdout.split("\n").first
5260
5351
 
5261
- command_result, exportable, new_lines = ux_block_eval_for_export(
5352
+ command_result = ux_block_eval_for_export(
5262
5353
  [assign_key_value_in_bash(export.name, export_init)],
5263
5354
  export,
5264
5355
  data: export.echo,
@@ -5271,7 +5362,7 @@ module MarkdownExec
5271
5362
 
5272
5363
  when :exec, ExportValueSource::EXEC
5273
5364
  # extract first line from 'exec' output
5274
- command_result, exportable, new_lines = ux_block_eval_for_export(
5365
+ command_result = ux_block_eval_for_export(
5275
5366
  bash_script_lines,
5276
5367
  export,
5277
5368
  data: export.exec,
@@ -5279,10 +5370,9 @@ module MarkdownExec
5279
5370
  force: force,
5280
5371
  silent: silent
5281
5372
  )
5282
-
5283
5373
  unless command_result.failure?
5284
5374
  export_init = command_result.stdout.split("\n").first
5285
- command_result, exportable, new_lines = ux_block_eval_for_export(
5375
+ command_result = ux_block_eval_for_export(
5286
5376
  [assign_key_value_in_bash(export.name, export_init)],
5287
5377
  export,
5288
5378
  data: export.exec,
@@ -5296,7 +5386,7 @@ module MarkdownExec
5296
5386
  else
5297
5387
  # first item from 'allow' list
5298
5388
  export_init = export.allow.first
5299
- command_result, exportable, new_lines = ux_block_eval_for_export(
5389
+ command_result = ux_block_eval_for_export(
5300
5390
  [assign_key_value_in_bash(export.name, export_init)],
5301
5391
  export,
5302
5392
  data: export.allow,
@@ -5309,13 +5399,14 @@ module MarkdownExec
5309
5399
  end
5310
5400
 
5311
5401
  when :default, UxActSource::DEFAULT
5312
- transformable = false
5313
- command_result = CommandResult.new(stdout: export.default.to_s)
5402
+ command_result = CommandResult.new(
5403
+ stdout: export.default.to_s
5404
+ )
5314
5405
 
5315
5406
  when :echo, UxActSource::ECHO
5316
5407
  raise unless export.echo.present?
5317
5408
 
5318
- command_result, exportable, new_lines = ux_block_eval_for_export(
5409
+ command_result = ux_block_eval_for_export(
5319
5410
  bash_script_lines,
5320
5411
  export,
5321
5412
  data: export.echo,
@@ -5327,7 +5418,7 @@ module MarkdownExec
5327
5418
  when :exec, UxActSource::EXEC
5328
5419
  raise unless export.exec.present?
5329
5420
 
5330
- command_result, exportable, new_lines = ux_block_eval_for_export(
5421
+ command_result = ux_block_eval_for_export(
5331
5422
  bash_script_lines,
5332
5423
  export,
5333
5424
  data: export.exec,
@@ -5337,7 +5428,7 @@ module MarkdownExec
5337
5428
 
5338
5429
  else
5339
5430
  export_init = export.init.to_s
5340
- command_result, exportable, new_lines = ux_block_eval_for_export(
5431
+ command_result = ux_block_eval_for_export(
5341
5432
  [assign_key_value_in_bash(export.name, export_init)],
5342
5433
  export,
5343
5434
  data: export.exec,
@@ -5349,17 +5440,15 @@ module MarkdownExec
5349
5440
  end
5350
5441
 
5351
5442
  # add message for required variables
5352
- if command_result.exit_status == EXIT_STATUS_REQUIRED_EMPTY
5443
+ if command_result.exit_status == CommandResult::EXIT_STATUS_REQUIRED_EMPTY
5353
5444
  command_result.warning = warning_required_empty(export)
5354
5445
  warn command_result.warning unless silent
5355
5446
  end
5356
5447
 
5357
- command_result.exportable = exportable
5358
- command_result.transformable = transformable
5359
- command_result.new_lines = new_lines
5448
+ command_result.exportable = command_result.success? && exportable
5360
5449
  command_result
5361
5450
  rescue StandardError
5362
- wwe bash_script_lines, export
5451
+ wwe $!, bash_script_lines, export
5363
5452
  end
5364
5453
 
5365
5454
  # true if the variable is exported in a series of evaluations
@@ -5491,7 +5580,8 @@ module MarkdownExec
5491
5580
 
5492
5581
  elsif block_is_shell(@dml_block_state.block)
5493
5582
  debounce_reset
5494
- vux_input_and_execute_shell_commands(stream: $stderr, shell: shell)
5583
+ vux_input_and_execute_shell_commands(stream: $stderr,
5584
+ shell: @delegate_object[:shell])
5495
5585
  return pause_user_exit ? :break : nil
5496
5586
 
5497
5587
  elsif block_is_view(@dml_block_state.block)
@@ -5992,6 +6082,10 @@ module MarkdownExec
5992
6082
  determine_block_state(selected_option)
5993
6083
  end
5994
6084
 
6085
+ def warning_failure(export)
6086
+ "A value must exist for: #{export.required.join(', ')}"
6087
+ end
6088
+
5995
6089
  def warning_required_empty(export)
5996
6090
  "A value must exist for: #{export.required.join(', ')}"
5997
6091
  end
@@ -7211,12 +7305,16 @@ module MarkdownExec
7211
7305
  def setup
7212
7306
  @hd = HashDelegator.new
7213
7307
  @bash_script_lines = ['#!/bin/bash', 'set -e']
7214
- @export = OpenStruct.new(name: 'TEST_VAR', required: ['TEST_VAR'])
7308
+ @export = OpenStruct.new(
7309
+ name: 'TEST_VAR', required: ['TEST_VAR'], init: 'default'
7310
+ )
7215
7311
  @mock_result = OpenStruct.new(
7216
- exit_status: 0,
7217
- stdout: 'test_output',
7218
- stderr: '',
7219
- warning: nil
7312
+ command_result: OpenStruct.new(
7313
+ exit_status: 0,
7314
+ stdout: 'test_output',
7315
+ stderr: '',
7316
+ warning: nil
7317
+ )
7220
7318
  )
7221
7319
  end
7222
7320
 
@@ -7229,29 +7327,27 @@ module MarkdownExec
7229
7327
 
7230
7328
  # Test string input - typical case
7231
7329
  def test_string_input_success
7232
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7233
- true])
7234
7330
  @hd.stubs(:variable_is_exportable).returns(true)
7235
7331
 
7236
- result = @hd.ux_block_eval_for_export(
7237
- @bash_script_lines, @export,
7332
+ command_result = @hd.ux_block_eval_for_export(
7333
+ @bash_script_lines,
7334
+ @export,
7238
7335
  data: 'echo "hello"',
7239
7336
  force: false,
7240
7337
  silent: false
7241
7338
  )
7242
7339
 
7243
- command_result, exportable, new_lines = result
7244
- assert_equal @mock_result, command_result
7245
- assert_equal true, exportable
7246
- assert_equal 1, new_lines.length
7247
- assert_equal 'TEST_VAR', new_lines.first[:name]
7248
- assert_equal 'test_output', new_lines.first[:text]
7340
+ # assert_equal @mock_result, command_result
7341
+ assert_equal 1, command_result.new_lines.length
7342
+ assert_equal 'TEST_VAR', command_result.new_lines.first[:name]
7343
+ assert_equal "hello\n", command_result.stdout
7249
7344
  end
7250
7345
 
7251
7346
  # Test string input with printf_expand
7252
7347
  def test_string_input_with_printf_expand
7253
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7254
- true])
7348
+ HashDelegator.stubs(:execute_bash_script_lines).returns(
7349
+ @mock_result
7350
+ )
7255
7351
  @hd.stubs(:variable_is_exportable).returns(true)
7256
7352
 
7257
7353
  @hd.ux_block_eval_for_export(
@@ -7268,198 +7364,180 @@ module MarkdownExec
7268
7364
 
7269
7365
  # Test integer input
7270
7366
  def test_integer_input
7271
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7272
- true])
7367
+ @mock_result.exportable = true
7368
+ HashDelegator.stubs(:execute_bash_script_lines).returns(
7369
+ @mock_result
7370
+ )
7273
7371
  @hd.stubs(:variable_is_exportable).returns(true)
7274
7372
 
7275
- result = @hd.ux_block_eval_for_export(
7373
+ command_result = @hd.ux_block_eval_for_export(
7276
7374
  @bash_script_lines, @export,
7277
7375
  data: 42,
7278
7376
  force: true,
7279
7377
  silent: false
7280
7378
  )
7281
7379
 
7282
- command_result, exportable, new_lines = result
7283
7380
  assert_equal @mock_result, command_result
7284
- assert_equal true, exportable
7285
- assert_equal 1, new_lines.length
7381
+ assert_equal 1, command_result.new_lines.length
7286
7382
  end
7287
7383
 
7288
7384
  # Test boolean inputs
7289
7385
  def test_boolean_true_input
7290
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7291
- true])
7386
+ @mock_result.exportable = true
7387
+ HashDelegator.stubs(:execute_bash_script_lines).returns(
7388
+ @mock_result
7389
+ )
7292
7390
  @hd.stubs(:variable_is_exportable).returns(true)
7293
7391
 
7294
- result = @hd.ux_block_eval_for_export(
7392
+ command_result = @hd.ux_block_eval_for_export(
7295
7393
  @bash_script_lines, @export,
7296
7394
  data: true,
7297
7395
  force: false,
7298
7396
  silent: false
7299
7397
  )
7300
7398
 
7301
- command_result, exportable, = result
7302
7399
  assert_equal @mock_result, command_result
7303
- assert_equal true, exportable
7304
7400
  end
7305
7401
 
7306
7402
  def test_boolean_false_input
7307
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7308
- true])
7403
+ @mock_result.exportable = true
7404
+ HashDelegator.stubs(:execute_bash_script_lines).returns(
7405
+ @mock_result
7406
+ )
7309
7407
  @hd.stubs(:variable_is_exportable).returns(true)
7310
7408
 
7311
- result = @hd.ux_block_eval_for_export(
7409
+ command_result = @hd.ux_block_eval_for_export(
7312
7410
  @bash_script_lines, @export,
7313
7411
  data: false,
7314
7412
  force: false,
7315
7413
  silent: false
7316
7414
  )
7317
7415
 
7318
- command_result, exportable, = result
7319
7416
  assert_equal @mock_result, command_result
7320
- assert_equal true, exportable
7321
7417
  end
7322
7418
 
7323
7419
  # Test float input
7324
7420
  def test_float_input
7325
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7326
- true])
7421
+ @mock_result.exportable = true
7422
+ HashDelegator.stubs(:execute_bash_script_lines).returns(
7423
+ @mock_result
7424
+ )
7327
7425
  @hd.stubs(:variable_is_exportable).returns(true)
7328
7426
 
7329
- result = @hd.ux_block_eval_for_export(
7427
+ command_result = @hd.ux_block_eval_for_export(
7330
7428
  @bash_script_lines, @export,
7331
7429
  data: 3.14,
7332
7430
  force: false,
7333
7431
  silent: false
7334
7432
  )
7335
7433
 
7336
- command_result, exportable, = result
7337
7434
  assert_equal @mock_result, command_result
7338
- assert_equal true, exportable
7339
7435
  end
7340
7436
 
7341
7437
  # Test hash input - typical case
7342
7438
  def test_hash_input_success
7343
7439
  hash_data = { 'VAR1' => 'value1', 'VAR2' => 'value2' }
7344
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7345
- true])
7440
+ @mock_result.exportable = true
7441
+ HashDelegator.stubs(:execute_bash_script_lines).returns(
7442
+ @mock_result
7443
+ )
7346
7444
  @hd.stubs(:variable_is_exportable).returns(true)
7347
7445
  @hd.stubs(:code_line_to_assign_a_variable).returns('VAR1=value1')
7348
7446
 
7349
- result = @hd.ux_block_eval_for_export(
7447
+ command_result = @hd.ux_block_eval_for_export(
7350
7448
  @bash_script_lines, @export,
7351
7449
  data: hash_data,
7352
7450
  force: false,
7353
7451
  silent: false
7354
7452
  )
7355
7453
 
7356
- command_result, exportable, new_lines = result
7357
7454
  assert_equal @mock_result, command_result
7358
- assert_equal true, exportable
7359
- assert_equal 2, new_lines.length
7360
- assert_equal 'VAR1', new_lines.first[:name]
7361
- assert_equal 'VAR2', new_lines.last[:name]
7455
+ assert_equal 2, command_result.new_lines.length
7456
+ assert_equal 'VAR1', command_result.new_lines.first[:name]
7457
+ assert_equal 'VAR2', command_result.new_lines.last[:name]
7362
7458
  end
7363
7459
 
7364
7460
  # Test hash input with first_only flag
7365
7461
  def test_hash_input_first_only
7366
7462
  hash_data = { 'VAR1' => 'value1', 'VAR2' => 'value2' }
7367
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7368
- true])
7369
- @hd.stubs(:variable_is_exportable).returns(true)
7370
- @hd.stubs(:code_line_to_assign_a_variable).returns('VAR1=value1')
7371
7463
 
7372
- result = @hd.ux_block_eval_for_export(
7464
+ command_result = @hd.ux_block_eval_for_export(
7373
7465
  @bash_script_lines, @export,
7374
7466
  data: hash_data,
7375
7467
  first_only: true,
7376
7468
  force: false,
7469
+ printf_expand: true,
7377
7470
  silent: false
7378
7471
  )
7379
7472
 
7380
- command_result, _, new_lines = result
7381
- assert_equal @mock_result, command_result
7382
- assert_equal 1, new_lines.length
7383
- assert_equal 'VAR1', new_lines.first[:name]
7473
+ assert_equal 1, command_result.new_lines.length
7474
+ assert_equal 'VAR1', command_result.new_lines.first[:name]
7384
7475
  end
7385
7476
 
7386
7477
  # Test hash with non-exportable variables
7387
7478
  def test_hash_input_non_exportable_variables
7388
7479
  hash_data = { 'LOCAL_VAR' => 'value1' }
7389
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7390
- true])
7480
+ @mock_result.exportable = true
7481
+ @mock_result.new_lines = []
7482
+ HashDelegator.stubs(:execute_bash_script_lines).returns(@mock_result)
7391
7483
  @hd.stubs(:variable_is_exportable).returns(false)
7392
7484
  @hd.stubs(:code_line_to_assign_a_variable).returns('LOCAL_VAR=value1')
7393
7485
 
7394
- result = @hd.ux_block_eval_for_export(
7486
+ command_result = @hd.ux_block_eval_for_export(
7395
7487
  @bash_script_lines, @export,
7396
7488
  data: hash_data,
7397
7489
  force: false,
7398
7490
  silent: false
7399
7491
  )
7400
7492
 
7401
- command_result, _, new_lines = result
7402
7493
  assert_equal @mock_result, command_result
7403
- assert_equal 0, new_lines.length # No exportable variables
7494
+ assert_equal 0, command_result.new_lines.length # No exportable variables
7404
7495
  end
7405
7496
 
7406
7497
  # Test EXIT_STATUS_REQUIRED_EMPTY handling
7407
7498
  def test_exit_status_required_empty_with_warning
7408
- failed_result = OpenStruct.new(
7409
- exit_status: 248, # EXIT_STATUS_REQUIRED_EMPTY
7499
+ failed_result = CommandResult.new(
7500
+ exit_status: CommandResult::EXIT_STATUS_REQUIRED_EMPTY,
7501
+ exportable: false,
7410
7502
  stdout: '',
7411
7503
  stderr: 'Required variable empty',
7412
7504
  warning: nil
7413
7505
  )
7414
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([failed_result,
7415
- false])
7506
+ HashDelegator.stubs(:execute_bash_script_lines).returns(failed_result)
7416
7507
  @hd.stubs(:warning_required_empty).returns('Warning: required empty')
7417
7508
 
7418
- result = @hd.ux_block_eval_for_export(
7509
+ command_result = @hd.ux_block_eval_for_export(
7419
7510
  @bash_script_lines, @export,
7420
7511
  data: 'echo ""',
7421
7512
  force: false,
7422
7513
  silent: false
7423
7514
  )
7424
7515
 
7425
- command_result, exportable, new_lines = result
7426
7516
  assert_equal failed_result, command_result
7427
- assert_equal false, exportable
7428
- assert_equal 0, new_lines.length
7429
7517
  assert_equal 'Warning: required empty', command_result.warning
7430
7518
  end
7431
7519
 
7432
7520
  # Test EXIT_STATUS_REQUIRED_EMPTY handling with silent flag
7433
7521
  def test_exit_status_required_empty_silent
7434
- failed_result = OpenStruct.new(
7435
- exit_status: 248,
7436
- stdout: '',
7437
- stderr: 'Required variable empty',
7438
- warning: nil
7439
- )
7440
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([failed_result,
7441
- false])
7442
-
7443
- result = @hd.ux_block_eval_for_export(
7522
+ command_result = @hd.ux_block_eval_for_export(
7444
7523
  @bash_script_lines, @export,
7445
7524
  data: 'echo ""',
7446
7525
  force: false,
7447
7526
  silent: true
7448
7527
  )
7449
7528
 
7450
- command_result, exportable, = result
7451
- assert_equal failed_result, command_result
7452
- assert_equal false, exportable
7453
- assert_nil command_result.warning # No warning when silent
7529
+ assert_empty command_result.warning # No warning when silent
7454
7530
  end
7455
7531
 
7456
7532
  # Test string parameter override
7457
7533
  def test_string_parameter_override
7458
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7459
- true])
7534
+ @mock_result.exportable = true
7535
+ HashDelegator.stubs(:execute_bash_script_lines).returns(
7536
+ @mock_result
7537
+ )
7460
7538
  @hd.stubs(:variable_is_exportable).returns(true)
7461
7539
 
7462
- result = @hd.ux_block_eval_for_export(
7540
+ command_result = @hd.ux_block_eval_for_export(
7463
7541
  @bash_script_lines, @export,
7464
7542
  data: 'original_data',
7465
7543
  string: 'override_string',
@@ -7468,136 +7546,115 @@ module MarkdownExec
7468
7546
  )
7469
7547
 
7470
7548
  # The function should use string parameter instead of data
7471
- command_result, exportable, = result
7472
7549
  assert_equal @mock_result, command_result
7473
- assert_equal true, exportable
7474
7550
  end
7475
7551
 
7476
7552
  # Edge case: empty string
7477
7553
  def test_empty_string_input
7478
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7479
- true])
7554
+ @mock_result.exportable = true
7555
+ HashDelegator.stubs(:execute_bash_script_lines).returns(
7556
+ @mock_result
7557
+ )
7480
7558
  @hd.stubs(:variable_is_exportable).returns(true)
7481
7559
 
7482
- result = @hd.ux_block_eval_for_export(
7560
+ command_result = @hd.ux_block_eval_for_export(
7483
7561
  @bash_script_lines, @export,
7484
7562
  data: '',
7485
7563
  force: false,
7486
7564
  silent: false
7487
7565
  )
7488
7566
 
7489
- command_result, exportable, = result
7490
7567
  assert_equal @mock_result, command_result
7491
- assert_equal true, exportable
7492
7568
  end
7493
7569
 
7494
7570
  # Edge case: nil data (uses string.nil? check)
7495
7571
  def test_nil_data_input
7496
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7497
- true])
7572
+ @mock_result.exportable = true
7573
+ HashDelegator.stubs(:execute_bash_script_lines).returns(
7574
+ @mock_result
7575
+ )
7498
7576
  @hd.stubs(:variable_is_exportable).returns(true)
7499
7577
 
7500
- result = @hd.ux_block_eval_for_export(
7578
+ command_result = @hd.ux_block_eval_for_export(
7501
7579
  @bash_script_lines, @export,
7502
7580
  data: 'd1',
7503
7581
  force: false,
7504
7582
  silent: false
7505
7583
  )
7506
7584
 
7507
- command_result, exportable, = result
7508
7585
  assert_equal @mock_result, command_result
7509
- assert_equal true, exportable
7510
7586
  end
7511
7587
 
7512
7588
  # Edge case: empty hash
7513
7589
  def test_empty_hash_input
7514
- result = @hd.ux_block_eval_for_export(
7590
+ command_result = @hd.ux_block_eval_for_export(
7515
7591
  @bash_script_lines, @export,
7516
7592
  data: {},
7517
7593
  force: false,
7518
7594
  silent: false
7519
7595
  )
7520
7596
 
7521
- command_result, exportable, new_lines = result
7522
7597
  assert_nil command_result # No iterations, so command_result remains nil
7523
- assert_equal true, exportable # Initial value
7524
- assert_equal 0, new_lines.length
7525
7598
  end
7526
7599
 
7527
7600
  # Edge case: hash with nil values
7528
7601
  def test_hash_with_nil_values
7529
7602
  hash_data = { 'VAR1' => nil, 'VAR2' => 'value2' }
7530
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7531
- true])
7532
- @hd.stubs(:variable_is_exportable).returns(true)
7533
- @hd.stubs(:code_line_to_assign_a_variable).returns('VAR1=')
7534
-
7535
- result = @hd.ux_block_eval_for_export(
7603
+ command_result = @hd.ux_block_eval_for_export(
7536
7604
  @bash_script_lines, @export,
7537
7605
  data: hash_data,
7538
7606
  force: false,
7607
+ printf_expand: true,
7539
7608
  silent: false
7540
7609
  )
7541
7610
 
7542
- command_result, exportable, new_lines = result
7543
- assert_equal @mock_result, command_result
7544
- assert_equal true, exportable
7545
- assert_equal 2, new_lines.length
7611
+ assert_equal 2, command_result.new_lines.length
7546
7612
  end
7547
7613
 
7548
7614
  # Edge case: unsupported input type (Array)
7549
7615
  def test_unsupported_input_type_array
7550
- result = @hd.ux_block_eval_for_export(
7616
+ command_result = @hd.ux_block_eval_for_export(
7551
7617
  @bash_script_lines, @export,
7552
7618
  data: %w[item1 item2],
7553
7619
  force: false,
7554
7620
  silent: false
7555
7621
  )
7556
7622
 
7557
- command_result, exportable, new_lines = result
7558
7623
  assert_nil command_result # No processing for unsupported types
7559
- assert_equal true, exportable # Initial value
7560
- assert_equal 0, new_lines.length
7561
7624
  end
7562
7625
 
7563
7626
  # Edge case: unsupported input type (custom object)
7564
7627
  def test_unsupported_input_type_object
7565
7628
  custom_object = Object.new
7566
- result = @hd.ux_block_eval_for_export(
7629
+ command_result = @hd.ux_block_eval_for_export(
7567
7630
  @bash_script_lines, @export,
7568
7631
  data: custom_object,
7569
7632
  force: false,
7570
7633
  silent: false
7571
7634
  )
7572
7635
 
7573
- command_result, exportable, new_lines = result
7574
7636
  assert_nil command_result # No processing for unsupported types
7575
- assert_equal true, exportable # Initial value
7576
- assert_equal 0, new_lines.length
7577
7637
  end
7578
7638
 
7579
7639
  # Test error handling - StandardError rescue
7580
7640
  def test_standard_error_rescue
7581
7641
  # Should not raise, but return nil due to rescue block
7582
- result = @hd.ux_block_eval_for_export(
7642
+ command_result = @hd.ux_block_eval_for_export(
7583
7643
  @bash_script_lines, @export,
7584
7644
  data: 'test_data',
7585
7645
  force: false,
7586
7646
  silent: false
7587
7647
  )
7588
7648
 
7589
- command_result, = result
7590
- assert_equal 127, command_result.exit_status
7649
+ assert_equal CommandResult::EXIT_STATUS_FAIL, command_result.exit_status
7591
7650
  end
7592
7651
 
7593
7652
  # Test environment variable setting
7594
7653
  def test_environment_variable_setting
7595
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7596
- true])
7597
7654
  @hd.stubs(:variable_is_exportable).returns(true)
7598
7655
 
7599
7656
  # Mock EnvInterface.set to verify it's called
7600
- EnvInterface.expects(:set).with('TEST_VAR', 'test_output')
7657
+ EnvInterface.expects(:set).with('TEST_VAR', "test\n")
7601
7658
 
7602
7659
  @hd.ux_block_eval_for_export(
7603
7660
  @bash_script_lines, @export,
@@ -7609,10 +7666,6 @@ module MarkdownExec
7609
7666
 
7610
7667
  # Test join_array_of_arrays is called correctly
7611
7668
  def test_join_array_of_arrays_called
7612
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7613
- true])
7614
- @hd.stubs(:variable_is_exportable).returns(true)
7615
-
7616
7669
  # Mock join_array_of_arrays to verify it's called with correct parameters
7617
7670
  @hd.expects(:join_array_of_arrays).with(
7618
7671
  @bash_script_lines,