markdown_exec 3.5.1 → 3.6.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 (127) 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 +19 -1
  7. data/Gemfile.lock +1 -1
  8. data/README.md +1 -1
  9. data/Rakefile +2 -0
  10. data/ai-principles.md +516 -0
  11. data/architecture-decisions.md +190 -0
  12. data/bats/block-hide.bats +1 -1
  13. data/bats/block-type-bash.bats +5 -5
  14. data/bats/block-type-link.bats +1 -1
  15. data/bats/block-type-opts.bats +3 -3
  16. data/bats/block-type-port.bats +2 -2
  17. data/bats/block-type-shell-context-eval.bats +48 -0
  18. data/bats/block-type-shell-require-ux.bats +2 -2
  19. data/bats/block-type-ux-allowed.bats +4 -4
  20. data/bats/block-type-ux-auto.bats +1 -1
  21. data/bats/block-type-ux-chained.bats +1 -1
  22. data/bats/block-type-ux-default.bats +1 -1
  23. data/bats/block-type-ux-echo-hash-transform.bats +1 -1
  24. data/bats/block-type-ux-echo-hash.bats +2 -2
  25. data/bats/block-type-ux-echo.bats +4 -4
  26. data/bats/block-type-ux-exec-hash-transform.bats +1 -1
  27. data/bats/block-type-ux-exec-hash.bats +2 -2
  28. data/bats/block-type-ux-exec.bats +1 -1
  29. data/bats/block-type-ux-force.bats +2 -2
  30. data/bats/block-type-ux-formats.bats +1 -1
  31. data/bats/block-type-ux-hidden.bats +1 -1
  32. data/bats/block-type-ux-invalid.bats +2 -2
  33. data/bats/block-type-ux-readonly.bats +1 -1
  34. data/bats/block-type-ux-require-chained.bats +2 -2
  35. data/bats/block-type-ux-require-context.bats +2 -2
  36. data/bats/block-type-ux-require.bats +3 -3
  37. data/bats/block-type-ux-required-variables.bats +1 -1
  38. data/bats/block-type-ux-row-format.bats +1 -1
  39. data/bats/block-type-ux-sources.bats +4 -4
  40. data/bats/block-type-ux-transform.bats +1 -1
  41. data/bats/block-type-vars.bats +3 -3
  42. data/bats/border.bats +1 -1
  43. data/bats/cli.bats +11 -11
  44. data/bats/command-substitution-options.bats +2 -2
  45. data/bats/command-substitution.bats +1 -1
  46. data/bats/document-shell.bats +3 -3
  47. data/bats/history.bats +5 -5
  48. data/bats/import-conflict.bats +1 -1
  49. data/bats/import-directive-line-continuation.bats +1 -1
  50. data/bats/import-directive-parameter-symbols.bats +1 -1
  51. data/bats/import-duplicates.bats +6 -6
  52. data/bats/import-parameter-symbols.bats +1 -1
  53. data/bats/import-with-text-substitution.bats +1 -1
  54. data/bats/import.bats +4 -4
  55. data/bats/indented-block-type-vars.bats +1 -1
  56. data/bats/indented-multi-line-output.bats +1 -1
  57. data/bats/line-decor-dynamic.bats +1 -1
  58. data/bats/line-wrapping.bats +1 -1
  59. data/bats/load-vars-state-demo.bats +8 -8
  60. data/bats/markup.bats +4 -4
  61. data/bats/mde.bats +4 -4
  62. data/bats/option-expansion.bats +1 -1
  63. data/bats/options-collapse.bats +4 -4
  64. data/bats/options.bats +47 -17
  65. data/bats/plain.bats +1 -1
  66. data/bats/publish.bats +2 -2
  67. data/bats/table-column-truncate.bats +1 -1
  68. data/bats/table.bats +2 -2
  69. data/bats/variable-expansion-multiline.bats +1 -1
  70. data/bats/variable-expansion.bats +6 -6
  71. data/bin/tab_completion.sh +3 -3
  72. data/conversation-template.md +611 -0
  73. data/docs/block-execution-modes.md +177 -0
  74. data/docs/block-filtering.md +252 -0
  75. data/docs/block-naming-patterns.md +210 -0
  76. data/docs/block-scanning-patterns.md +248 -0
  77. data/docs/cli-reference.md +370 -0
  78. data/docs/dev/bats-document-configuration.md +1 -1
  79. data/docs/dev/block-hide.md +1 -1
  80. data/docs/dev/block-type-shell-context-eval.md +52 -0
  81. data/docs/dev/block-type-shell-require-ux.md +6 -2
  82. data/docs/dev/block-type-ux-echo.md +3 -3
  83. data/docs/dev/block-type-ux-force.md +1 -1
  84. data/docs/dev/block-type-ux-require-chained.md +1 -1
  85. data/docs/dev/block-type-ux-require.md +2 -2
  86. data/docs/dev/block-type-ux-transform.md +5 -4
  87. data/docs/dev/import-parameter-symbols.md +1 -1
  88. data/docs/dev/linked-file.md +1 -1
  89. data/docs/dev/load-vars-state-demo.md +1 -1
  90. data/docs/dev/print_bytes.md +3 -0
  91. data/docs/dev/requiring-blocks.md +1 -1
  92. data/docs/dev/shebang.md +6 -0
  93. data/docs/dev/specs.md +2 -2
  94. data/docs/docker-testing.md +5 -0
  95. data/docs/execution-control.md +384 -0
  96. data/docs/getting-started.md +209 -0
  97. data/docs/import-options.md +391 -0
  98. data/docs/shell-script-evaluation.md +78 -0
  99. data/docs/tab-completion.md +7 -0
  100. data/docs/ux-blocks.md +376 -0
  101. data/examples/link-blocks-vars.md +2 -2
  102. data/examples/linked.md +1 -1
  103. data/examples/linked1.md +1 -1
  104. data/examples/opts-blocks.md +2 -2
  105. data/examples/port-blocks.md +1 -1
  106. data/examples/variable-expansion.md +1 -1
  107. data/implementation-decisions.md +212 -0
  108. data/lib/cached_nested_file_reader.rb +145 -5
  109. data/lib/command_result.rb +27 -6
  110. data/lib/executed_shell_command.rb +512 -0
  111. data/lib/filter.rb +7 -7
  112. data/lib/hash_delegator.rb +699 -605
  113. data/lib/input_sequencer.rb +4 -3
  114. data/lib/link_history.rb +95 -36
  115. data/lib/markdown_exec/version.rb +1 -1
  116. data/lib/mdoc.rb +138 -51
  117. data/lib/menu.src.yml +115 -88
  118. data/lib/menu.yml +154 -88
  119. data/lib/transformed_shell_command.rb +449 -0
  120. data/lib/wl.rb +15 -0
  121. data/lib/ww.rb +17 -6
  122. data/requirements.md +111 -0
  123. data/semantic-tokens.md +132 -0
  124. data/tasks.md +69 -0
  125. metadata +29 -4
  126. data/docs/ux-blocks-examples.md +0 -120
  127. 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
+ # transient_code: ["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
+ # transient_code: ["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
+ transient_code: [],
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(transient_code))
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: transient_code,
286
+ stdout: tsc.transformed_output,
287
+ transformed_shell_command: tsc
288
+ )
289
+ end
290
+ rescue StandardError
291
+ wwe $!, 'transient_code:', transient_code, '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: transient_code,
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,11 +777,11 @@ 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
663
- @dml_link_state = Struct.new(:document_filename, :inherited_lines)
784
+ @dml_link_state = Struct.new(:document_filename, :context_code)
664
785
  .new(@delegate_object[:filename], [])
665
786
  @dml_menu_blocks = []
666
787
  @fcb_store = [] # all fcbs created
@@ -729,7 +850,7 @@ module MarkdownExec
729
850
  add_inherited_lines(
730
851
  link_state: link_state,
731
852
  menu_blocks: menu_blocks
732
- ) if @delegate_object[:menu_with_inherited_lines]
853
+ ) if @delegate_object[:menu_with_context_code]
733
854
 
734
855
  # back before exit
735
856
  add_back_option(
@@ -848,10 +969,10 @@ module MarkdownExec
848
969
  # @param menu_blocks [Array] The array of menu block elements.
849
970
  # @param position [Symbol] The position to insert the divider (:initial or :final).
850
971
  def append_inherited_lines(link_state:, menu_blocks:, position: top)
851
- return unless link_state.inherited_lines_present?
972
+ return unless link_state.context_code_present?
852
973
 
853
974
  insert_at_top = @delegate_object[:menu_inherited_lines_at_top]
854
- chrome_blocks = link_state.inherited_lines_map do |line|
975
+ chrome_blocks = link_state.context_code_map do |line|
855
976
  formatted = format(@delegate_object[:menu_inherited_lines_format],
856
977
  { line: line })
857
978
  persist_fcb(
@@ -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
@@ -1070,7 +1190,7 @@ module MarkdownExec
1070
1190
  occurrence_expressions: nil
1071
1191
  )
1072
1192
  evaluate_shell_expressions(
1073
- (link_state&.inherited_lines_block || ''),
1193
+ (link_state&.context_code_block || ''),
1074
1194
  commands,
1075
1195
  initial_code_required: initial_code_required,
1076
1196
  occurrence_expressions: occurrence_expressions
@@ -1089,7 +1209,7 @@ module MarkdownExec
1089
1209
  exts: '.out.txt',
1090
1210
  saved_asset_format:
1091
1211
  shell_escape_asset_format(
1092
- code_lines: @dml_link_state.inherited_lines,
1212
+ transient_code: @dml_link_state.context_code,
1093
1213
  shell: ShellType::BASH
1094
1214
  )
1095
1215
  ).generate_name
@@ -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 inherited_lines_from_auto_blocks(
1268
+ all_blocks, mdoc: nil, default_only: true, context_code: []
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
+ required_code = 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
+ context_code: context_code
1298
+ )
1299
+ block_inherit = required_code[:context_code]
1300
+
1301
+ required_code[:transient_code]
1173
1302
  else
1174
1303
  fcb.body
1175
1304
  end
@@ -1207,80 +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.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)
1247
- end
1248
-
1249
- # return code resulting from evaluating all UX blocks in order
1250
- def code_from_auto_ux_blocks(
1251
- all_blocks,
1252
- mdoc
1253
- )
1254
- # do not reload the most recent filename
1255
- unless @ux_most_recent_filename != @delegate_object[:filename]
1256
- return
1257
- end
1258
-
1259
- @ux_most_recent_filename = @delegate_object[:filename]
1260
-
1261
- # select all UX blocks, rejecting non-primary split
1262
- blocks = select_automatic_ux_blocks(
1263
- all_blocks.reject(&:is_split_rest?)
1264
- )
1265
- return if blocks.empty?
1266
-
1267
- # collect code from all ux blocks
1268
- (blocks.each.with_object([]) do |block, merged_options|
1269
- command_result_w_e_t_nl =
1270
- code_from_ux_block_to_set_environment_variables(
1271
- block,
1272
- mdoc,
1273
- force: @delegate_object[:ux_auto_load_force_default],
1274
- only_default: true,
1275
- silent: true
1276
- )
1277
- if command_result_w_e_t_nl.failure?
1278
- # if a block fails, proceed with next block
1279
- merged_options
1280
- else
1281
- merged_options.push(command_result_w_e_t_nl.new_lines)
1282
- end
1283
- end).to_a
1344
+ wwe $!, 'all_blocks.count:', all_blocks.count
1284
1345
  end
1285
1346
 
1286
1347
  # return code resulting from evaluating all automatic VARS blocks in order
@@ -1317,91 +1378,101 @@ module MarkdownExec
1317
1378
  # set ENV value for the variable and return code lines for the same
1318
1379
  # for BlockType::UX
1319
1380
  def code_from_ux_block_to_set_environment_variables(
1320
- selected, mdoc, inherited_code: nil, force: true, only_default: false,
1321
- required: nil, silent:
1381
+ selected, mdoc, context_code: nil, force: true, only_default: false,
1382
+ required: OpenStruct.new(context_code: [], transient_code: []),
1383
+ silent:
1322
1384
  )
1323
1385
  wwt :fcb, 'selected:', selected
1386
+ wwt :context_code, context_code
1324
1387
  ret_command_result = nil
1325
1388
  exit_prompt = @delegate_object[:prompt_filespec_back]
1326
1389
 
1327
- required ||= mdoc.collect_recursively_required_code(
1390
+ wwt :required, required
1391
+ required_code = mdoc&.collect_recursively_required_code(
1328
1392
  anyname: selected_id_name(selected),
1329
1393
  label_format_above: @delegate_object[:shell_code_label_format_above],
1330
1394
  label_format_below: @delegate_object[:shell_code_label_format_below],
1331
- block_source: block_source
1395
+ block_source: block_source,
1396
+ context_code: context_code
1332
1397
  )
1333
- wwt :required, required
1398
+ wwt :required_code, required_code
1399
+ # new_inherited_lines = []
1334
1400
 
1335
1401
  # process each ux block in sequence, setting ENV and collecting lines
1336
1402
  concatenated_code_from_required_blocks = []
1337
- required[:blocks]&.each do |block|
1338
- next unless block.type == BlockType::UX
1339
-
1340
- wwt :fcb, 'a required block', block
1341
-
1342
- body1 = block.raw_body || block.body
1343
- case data = safe_yaml_load(body1.join("\n"))
1344
- when Hash
1345
- export = parse_yaml_of_ux_block(
1346
- data,
1347
- prompt: @delegate_object[:prompt_ux_enter_a_value],
1348
- validate: '^(?<name>[^ ].*)$'
1349
- )
1350
- block.export = export
1351
- block.export_act = FCB.act_source(export)
1352
- block.export_init = FCB.init_source(export)
1353
-
1354
- # required are variable names that must be set before the UX block is executed.
1355
- # if any precondition is not set, the sequence is aborted.
1356
- required_variables = []
1357
- export.required&.each do |precondition|
1358
- required_variables.push "[[ -z $#{precondition} ]] && exit #{EXIT_STATUS_REQUIRED_EMPTY}"
1359
- end
1360
- wwt :required_variables, 'required_variables',
1361
- required_variables
1362
-
1363
- eval_code = join_array_of_arrays(
1364
- inherited_code, # inherited code
1365
- concatenated_code_from_required_blocks, # current block requirements
1366
- required_variables, # test conditions
1367
- required[:code] # required by selected
1368
- )
1369
- wwt :eval_code, 'eval_code:', eval_code
1370
- if only_default
1371
- command_result_w_e_t_nl =
1372
- ux_block_export_automatic(eval_code, export)
1373
- # do not display warnings on initializing call
1374
- return command_result_w_e_t_nl if command_result_w_e_t_nl.failure?
1403
+ if required_code # exclude for tests
1404
+ required_code[:blocks]&.each do |block|
1405
+ next if block.type != BlockType::UX
1406
+
1407
+ wwt :fcb, 'a required block', block
1408
+
1409
+ body1 = block.raw_body || block.body
1410
+ case data = safe_yaml_load(body1.join("\n"))
1411
+ when Hash
1412
+ export = parse_yaml_of_ux_block(
1413
+ data,
1414
+ prompt: @delegate_object[:prompt_ux_enter_a_value],
1415
+ validate: '^(?<name>[^ ].*)$'
1416
+ )
1417
+ block.export = export
1418
+ block.export_act = FCB.act_source(export)
1419
+ block.export_init = FCB.init_source(export)
1420
+
1421
+ # required_code are variable names that must be set before the UX block is executed.
1422
+ # if any precondition is not set, the sequence is aborted.
1423
+ required_variables = []
1424
+ export.required&.each do |precondition|
1425
+ required_variables.push "[[ -z $#{precondition} ]] && exit #{CommandResult::EXIT_STATUS_REQUIRED_EMPTY}"
1426
+ end
1427
+ wwt :required_variables, 'required_variables',
1428
+ required_variables
1429
+
1430
+ eval_code = join_array_of_arrays(
1431
+ required[:context_code],
1432
+ context_code, # inherited code
1433
+ concatenated_code_from_required_blocks, # current block requirements
1434
+ required_variables, # test conditions
1435
+ required[:transient_code],
1436
+ required_code[:transient_code] # required by selected
1437
+ )
1438
+ wwt :eval_code, 'eval_code:', eval_code
1439
+ if only_default
1440
+ command_result_w_e_t_nl =
1441
+ ux_block_export_automatic(eval_code, export)
1442
+ # do not display warnings on initializing call
1443
+ return command_result_w_e_t_nl if command_result_w_e_t_nl.failure?
1375
1444
 
1376
- else
1377
- command_result_w_e_t_nl =
1378
- ux_block_export_activated(eval_code, export, exit_prompt)
1379
- if command_result_w_e_t_nl.failure?
1380
- if command_result_w_e_t_nl.warning&.present? && !silent
1381
- warn command_result_w_e_t_nl.warning
1445
+ else
1446
+ command_result_w_e_t_nl =
1447
+ ux_block_export_activated(eval_code, export, exit_prompt)
1448
+ if command_result_w_e_t_nl.failure?
1449
+ if command_result_w_e_t_nl.warning&.present? && !silent
1450
+ warn command_result_w_e_t_nl.warning
1451
+ end
1452
+ return command_result_w_e_t_nl
1382
1453
  end
1383
- return command_result_w_e_t_nl
1384
1454
  end
1385
- end
1386
- return command_result_w_e_t_nl if command_result_w_e_t_nl.failure?
1455
+ return command_result_w_e_t_nl if command_result_w_e_t_nl.failure?
1387
1456
 
1388
- # update the required lines for this and subsequent blocks
1389
- command_result_w_e_t_nl.new_lines =
1390
- process_command_result_lines(command_result_w_e_t_nl, export,
1391
- concatenated_code_from_required_blocks)
1392
- concatenated_code_from_required_blocks.concat(command_result_w_e_t_nl.new_lines)
1457
+ # update the required lines for this and subsequent blocks
1458
+ command_result_w_e_t_nl.new_lines =
1459
+ process_command_result_lines(command_result_w_e_t_nl, export,
1460
+ concatenated_code_from_required_blocks)
1461
+ concatenated_code_from_required_blocks.concat(command_result_w_e_t_nl.new_lines)
1393
1462
 
1394
- concatenated_code_from_required_blocks = annotate_required_lines(
1395
- 'blk:UX', concatenated_code_from_required_blocks, block_name: selected.id
1396
- )
1463
+ concatenated_code_from_required_blocks = annotate_required_lines(
1464
+ 'blk:UX', concatenated_code_from_required_blocks, block_name: selected.id
1465
+ )
1397
1466
 
1398
- command_result_w_e_t_nl.new_lines = concatenated_code_from_required_blocks
1399
- ret_command_result = command_result_w_e_t_nl
1400
- else
1401
- raise "Invalid data type: #{data.inspect}"
1467
+ command_result_w_e_t_nl.new_lines = concatenated_code_from_required_blocks
1468
+ ret_command_result = command_result_w_e_t_nl
1469
+ else
1470
+ raise "Invalid data type: #{data.inspect}"
1471
+ end
1402
1472
  end
1403
1473
  end
1404
- wwt :concatenated_code_from_required_blocks, concatenated_code_from_required_blocks
1474
+ wwt :concatenated_code_from_required_blocks,
1475
+ concatenated_code_from_required_blocks
1405
1476
 
1406
1477
  (ret_command_result || CommandResult.new(
1407
1478
  stdout: annotate_required_lines(
@@ -1411,19 +1482,19 @@ module MarkdownExec
1411
1482
  wwt :cr, ret, caller.deref
1412
1483
  end
1413
1484
  rescue StandardError
1414
- wwe 'selected:', selected, 'required:', required, 'block:', block,
1485
+ wwe $!, 'selected:', selected, 'required_code:', required_code, 'block:', block,
1415
1486
  'data:', data
1416
1487
  end
1417
1488
 
1418
1489
  # def
1419
1490
  # sets ENV
1420
1491
  def code_from_vars_block_to_set_environment_variables(selected)
1421
- code_lines = []
1492
+ transient_code = []
1422
1493
  case data = YAML.load(selected.body.join("\n"))
1423
1494
  when Hash
1424
1495
  data.each do |key, value|
1425
1496
  EnvInterface.set(key, value.to_s)
1426
- code_lines << assign_key_value_in_bash(key, value)
1497
+ transient_code << assign_key_value_in_bash(key, value)
1427
1498
 
1428
1499
  next unless @delegate_object[:menu_vars_set_format].present?
1429
1500
 
@@ -1435,10 +1506,10 @@ module MarkdownExec
1435
1506
  print string_send_color(formatted_string, :menu_vars_set_color)
1436
1507
  end
1437
1508
  end
1438
- annotate_required_lines('blk:VARS', code_lines, block_name: selected.id)
1509
+ annotate_required_lines('blk:VARS', transient_code, block_name: selected.id)
1439
1510
  rescue StandardError
1440
1511
  wwe 'selected:', selected, 'data:', data, 'key:', key, 'value:', value,
1441
- 'code_lines:', code_lines, 'formatted_string:', formatted_string
1512
+ 'transient_code:', transient_code, 'formatted_string:', formatted_string
1442
1513
  end
1443
1514
 
1444
1515
  # make a single line of shell code to assign an escaped value to a variable
@@ -1495,7 +1566,8 @@ module MarkdownExec
1495
1566
  else
1496
1567
  @run_state.in_own_window = false
1497
1568
  command_execute_in_process(
1498
- args: args, command: command,
1569
+ args: args,
1570
+ command: command,
1499
1571
  erls: erls,
1500
1572
  filename: @delegate_object[:filename],
1501
1573
  shell: shell
@@ -1577,8 +1649,7 @@ module MarkdownExec
1577
1649
  # containing code blocks.
1578
1650
  # @param selected [Hash] The selected item from the menu
1579
1651
  # to be executed.
1580
- # @return [LoadFileLinkState] An object indicating whether to load
1581
- # the next block or reuse the current one.
1652
+ # @return [Array] context_code
1582
1653
  def compile_execute_and_trigger_reuse(
1583
1654
  mdoc:, selected:, block_source:, link_state:
1584
1655
  )
@@ -1595,18 +1666,18 @@ module MarkdownExec
1595
1666
  end
1596
1667
  end
1597
1668
 
1598
- required_lines = execute_block_type_port_code_lines(
1669
+ required_code = execute_block_type_port_code_lines(
1599
1670
  mdoc: mdoc, selected: selected,
1600
1671
  link_state: link_state, block_source: block_source
1601
1672
  )
1602
1673
  output_or_approval = @delegate_object[:output_script] ||
1603
1674
  @delegate_object[:user_must_approve]
1604
1675
  if output_or_approval
1605
- display_required_code(required_lines: required_lines)
1676
+ display_required_code(required_lines: required_code[:transient_code])
1606
1677
  end
1607
1678
  allow_execution = if @delegate_object[:user_must_approve]
1608
1679
  prompt_for_user_approval(
1609
- required_lines: required_lines,
1680
+ required_lines: required_code[:transient_code],
1610
1681
  selected: selected
1611
1682
  )
1612
1683
  else
@@ -1614,16 +1685,28 @@ module MarkdownExec
1614
1685
  end
1615
1686
 
1616
1687
  if allow_execution
1688
+ script_lines = ( #HashDelegator.join_code_lines(
1689
+ # ['# prior context'] +
1690
+ (link_state&.context_code || []) +
1691
+ # ['# new context'] +
1692
+ required_code[:context_code] +
1693
+ # ['# new transient'] +
1694
+ required_code[:transient_code]
1695
+ )
1617
1696
  execute_required_lines(
1618
1697
  blockname: selected_id_name(selected),
1619
1698
  erls: { play_bin: play_bin,
1620
1699
  shell: selected_shell(selected.shell) },
1621
- required_lines: required_lines,
1700
+ script_lines: script_lines,
1622
1701
  shell: selected_shell(selected.shell)
1623
1702
  )
1624
1703
  end
1625
1704
 
1626
1705
  link_state.block_name = nil
1706
+
1707
+ required_code[:context_code]
1708
+ rescue StandardError
1709
+ wwe $!
1627
1710
  end
1628
1711
 
1629
1712
  # Check if the expression contains wildcard characters
@@ -1887,16 +1970,7 @@ module MarkdownExec
1887
1970
  case_conversion: criteria[:case_conversion],
1888
1971
  center: criteria[:center] &&
1889
1972
  @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
-
1973
+ collapse: fcb.collapse_token == COLLAPSIBLE_TOKEN_COLLAPSE,
1900
1974
  color_method: criteria[:color] &&
1901
1975
  @delegate_object[criteria[:color]].to_sym,
1902
1976
  decor_patterns:
@@ -1943,6 +2017,8 @@ module MarkdownExec
1943
2017
  # filter block if selected in menu
1944
2018
  return true if @run_state.source.block_name_from_cli
1945
2019
 
2020
+ return true if @delegate_object[:block_name].empty?
2021
+
1946
2022
  # return false if @prior_execution_block == @delegate_object[:block_name]
1947
2023
  if @prior_execution_block == @delegate_object[:block_name]
1948
2024
  return @allowed_execution_block == @prior_execution_block ||
@@ -2119,10 +2195,10 @@ module MarkdownExec
2119
2195
  warn format_and_highlight_lines(link_state.inherited_dependencies,
2120
2196
  label: 'inherited_dependencies')
2121
2197
  end
2122
- return unless @delegate_object[:dump_inherited_lines]
2198
+ return unless @delegate_object[:dump_context_code]
2123
2199
 
2124
- warn format_and_highlight_lines(link_state.inherited_lines,
2125
- label: 'inherited_lines')
2200
+ warn format_and_highlight_lines(link_state.context_code,
2201
+ label: 'context_code')
2126
2202
  end
2127
2203
 
2128
2204
  # Opens text in an editor for user modification and
@@ -2208,7 +2284,7 @@ module MarkdownExec
2208
2284
  vux_edit_inherited
2209
2285
  return :break if pause_user_exit
2210
2286
 
2211
- next_state_append_code(selected, link_state, [])
2287
+ next_state_append_code(selected, link_state, context_code: [])
2212
2288
 
2213
2289
  elsif selected.type == BlockType::HISTORY
2214
2290
  debounce_reset
@@ -2221,23 +2297,25 @@ module MarkdownExec
2221
2297
 
2222
2298
  elsif selected.type == BlockType::LINK
2223
2299
  debounce_reset
2224
- execute_block_type_link_with_state(link_block_body: selected.body,
2225
- mdoc: mdoc,
2226
- selected: selected,
2227
- link_state: link_state,
2228
- block_source: block_source)
2300
+ execute_block_type_link_with_state(
2301
+ link_block_body: selected.body,
2302
+ mdoc: mdoc,
2303
+ selected: selected,
2304
+ link_state: link_state,
2305
+ block_source: block_source
2306
+ )
2229
2307
 
2230
2308
  elsif selected.type == BlockType::LOAD
2231
2309
  debounce_reset
2232
2310
  load_result = execute_block_type_load_code_lines(selected)
2233
- next_state_append_code(selected, link_state, load_result.code,
2234
- mode: load_result.mode)
2311
+ next_state_append_code(selected, link_state, context_code: load_result.code,
2312
+ mode: load_result.mode)
2235
2313
 
2236
2314
  elsif selected.type == BlockType::SAVE
2237
2315
  debounce_reset
2238
2316
 
2239
2317
  execute_block_type_save(
2240
- code_lines: link_state&.inherited_lines,
2318
+ transient_code: link_state&.context_code,
2241
2319
  selected: selected
2242
2320
  )
2243
2321
 
@@ -2259,7 +2337,7 @@ module MarkdownExec
2259
2337
 
2260
2338
  elsif selected.type == BlockType::OPTS
2261
2339
  debounce_reset
2262
- code_lines = []
2340
+ transient_code = []
2263
2341
  options_state = read_show_options_and_trigger_reuse(
2264
2342
  link_state: link_state,
2265
2343
  mdoc: @dml_mdoc,
@@ -2268,7 +2346,7 @@ module MarkdownExec
2268
2346
  update_menu_base(options_state.options)
2269
2347
 
2270
2348
  link_state = LinkState.new
2271
- next_state_append_code(selected, link_state, code_lines)
2349
+ next_state_append_code(selected, link_state, context_code: transient_code)
2272
2350
 
2273
2351
  elsif selected.type == BlockType::PORT
2274
2352
  debounce_reset
@@ -2277,23 +2355,22 @@ module MarkdownExec
2277
2355
  selected: selected,
2278
2356
  link_state: link_state,
2279
2357
  block_source: block_source
2280
- )
2281
- next_state_set_code(selected, link_state, required_lines)
2358
+ )[:context_code]
2359
+ next_state_append_code(selected, link_state, context_code: required_lines)
2282
2360
 
2283
2361
  elsif selected.type == BlockType::UX
2284
2362
  debounce_reset
2285
2363
  command_result_w_e_t_nl = code_from_ux_block_to_set_environment_variables(
2286
2364
  selected,
2287
2365
  @dml_mdoc,
2288
- inherited_code: @dml_link_state.inherited_lines,
2366
+ context_code: @dml_link_state.context_code,
2289
2367
  only_default: false,
2290
2368
  silent: true
2291
2369
  )
2292
- ### TBD if command_result_w_e_t_nl.failure?
2293
2370
  next_state_append_code(
2294
2371
  selected,
2295
2372
  link_state,
2296
- command_result_w_e_t_nl.failure? ? [] : command_result_w_e_t_nl.new_lines
2373
+ context_code: command_result_w_e_t_nl.failure? ? [] : command_result_w_e_t_nl.new_lines
2297
2374
  )
2298
2375
 
2299
2376
  elsif selected.type == BlockType::VARS
@@ -2301,7 +2378,7 @@ module MarkdownExec
2301
2378
  next_state_append_code(
2302
2379
  selected,
2303
2380
  link_state,
2304
- code_from_vars_block_to_set_environment_variables(selected)
2381
+ context_code: code_from_vars_block_to_set_environment_variables(selected)
2305
2382
  )
2306
2383
 
2307
2384
  elsif COLLAPSIBLE_TYPES.include?(selected.type)
@@ -2310,11 +2387,14 @@ module MarkdownExec
2310
2387
  LoadFileLinkState.new(LoadFile::REUSE, link_state)
2311
2388
 
2312
2389
  elsif debounce_allows
2313
- compile_execute_and_trigger_reuse(mdoc: mdoc,
2314
- selected: selected,
2315
- link_state: link_state,
2316
- block_source: block_source)
2317
- LoadFileLinkState.new(LoadFile::REUSE, link_state)
2390
+ # SHELL type
2391
+ context_code = compile_execute_and_trigger_reuse(
2392
+ mdoc: mdoc,
2393
+ selected: selected,
2394
+ link_state: link_state,
2395
+ block_source: block_source
2396
+ )
2397
+ next_state_set_code(selected, link_state, context_code)
2318
2398
 
2319
2399
  else
2320
2400
  LoadFileLinkState.new(LoadFile::REUSE, link_state)
@@ -2408,29 +2488,31 @@ module MarkdownExec
2408
2488
  ## collect blocks specified by block
2409
2489
  #
2410
2490
  if mdoc
2411
- code_info = mdoc.collect_recursively_required_code(
2491
+ required_code = mdoc.collect_recursively_required_code(
2412
2492
  anyname: selected_id_name(selected),
2413
2493
  block_source: block_source,
2414
2494
  label_format_above: @delegate_object[:shell_code_label_format_above],
2415
- label_format_below: @delegate_object[:shell_code_label_format_below]
2495
+ label_format_below: @delegate_object[:shell_code_label_format_below],
2496
+ context_code: link_state&.context_code || []
2416
2497
  )
2417
- code_lines = annotate_required_lines(
2418
- 'blk:LINK', code_info[:code], block_name: selected.id
2498
+
2499
+ context_code = annotate_required_lines(
2500
+ 'blk:LINK', required_code[:context_code], block_name: selected.id
2419
2501
  )
2420
- block_names = code_info[:block_names]
2421
- dependencies = code_info[:dependencies]
2502
+ block_names = required_code[:block_names]
2503
+ dependencies = required_code[:dependencies]
2422
2504
  else
2423
2505
  block_names = []
2424
- code_lines = []
2506
+ context_code = []
2425
2507
  dependencies = {}
2426
2508
  end
2427
2509
 
2428
2510
  # load key and values from link block into current environment
2429
2511
  #
2430
2512
  if link_block_data[LinkKeys::VARS]
2431
- code_lines << BashCommentFormatter.format_comment(selected.pub_name)
2513
+ context_code << BashCommentFormatter.format_comment(selected.pub_name)
2432
2514
  link_block_data[LinkKeys::VARS].each do |key, value|
2433
- code_lines << assign_key_value_in_bash(key, value)
2515
+ context_code << assign_key_value_in_bash(key, value)
2434
2516
  EnvInterface.set(key, value.to_s)
2435
2517
  end
2436
2518
  end
@@ -2441,20 +2523,20 @@ module MarkdownExec
2441
2523
  load_filespec = load_filespec_from_expression(load_expr)
2442
2524
  if load_filespec
2443
2525
  begin
2444
- code_lines += File.readlines(load_filespec,
2445
- chomp: true)
2526
+ context_code += File.readlines(load_filespec,
2527
+ chomp: true)
2446
2528
  rescue Errno::ENOENT
2447
2529
  report_error($ERROR_INFO)
2448
2530
  end
2449
2531
  end
2450
2532
  end
2451
2533
 
2452
- # if an eval link block, evaluate code_lines and return its standard output
2534
+ # if an eval link block, evaluate context_code and return its standard output
2453
2535
  #
2454
2536
  if link_block_data.fetch(LinkKeys::EVAL, false) ||
2455
2537
  link_block_data.fetch(LinkKeys::EXEC, false)
2456
- code_lines = link_block_data_eval(
2457
- link_state, code_lines, selected, link_block_data,
2538
+ context_code = link_block_data_eval(
2539
+ link_state, context_code, selected, link_block_data,
2458
2540
  block_source: block_source,
2459
2541
  shell: @delegate_object[:block_type_default]
2460
2542
  )
@@ -2469,13 +2551,16 @@ module MarkdownExec
2469
2551
  nil
2470
2552
  ) || link_block_data.fetch(LinkKeys::BLOCK, nil) || ''
2471
2553
 
2472
- code_lines = annotate_required_lines(
2473
- 'blk:LINK', code_lines, block_name: selected.id
2554
+ context_code = annotate_required_lines(
2555
+ 'blk:LINK', context_code, block_name: selected.id
2556
+ )
2557
+ context_code = annotate_required_lines(
2558
+ 'blk:LINK', context_code, block_name: selected.id
2474
2559
  )
2475
2560
 
2476
2561
  if link_block_data[LinkKeys::RETURN]
2477
2562
  pop_add_current_code_to_head_and_trigger_load(
2478
- link_state, block_names, code_lines,
2563
+ link_state, block_names, context_code,
2479
2564
  dependencies, selected, next_block_name: next_block_name
2480
2565
  )
2481
2566
 
@@ -2488,8 +2573,8 @@ module MarkdownExec
2488
2573
  ((link_state&.inherited_block_names || []) + block_names).sort.uniq,
2489
2574
  inherited_dependencies:
2490
2575
  (link_state&.inherited_dependencies || {}).merge(dependencies || {}), ### merge, not replace, key data
2491
- inherited_lines: HashDelegator.flatten_and_compact_arrays(
2492
- link_state&.inherited_lines, code_lines
2576
+ context_code: HashDelegator.flatten_and_compact_arrays(
2577
+ link_state&.context_code, context_code
2493
2578
  ),
2494
2579
  keep_code: link_state&.keep_code,
2495
2580
  next_block_name: next_block_name,
@@ -2586,31 +2671,34 @@ module MarkdownExec
2586
2671
  #
2587
2672
  # @param mdoc [YourMDocClass] An instance of the MDoc class.
2588
2673
  # @param selected [Hash] The selected block.
2589
- # @return [Array<String>] Required code blocks as an array of lines.
2674
+ # @return [OpenStruct] context and transient code
2590
2675
  def execute_block_type_port_code_lines(mdoc:, selected:, block_source:,
2591
2676
  link_state: LinkState.new)
2592
- required = mdoc.collect_recursively_required_code(
2677
+ required_code = mdoc.collect_recursively_required_code(
2593
2678
  anyname: selected_id_name(selected),
2594
2679
  label_format_above: @delegate_object[:shell_code_label_format_above],
2595
2680
  label_format_below: @delegate_object[:shell_code_label_format_below],
2596
- block_source: block_source
2597
- ) # !!t 'required'
2681
+ block_source: block_source,
2682
+ context_code: link_state&.context_code || []
2683
+ ) # !!t 'required_code'
2598
2684
  dependencies = (
2599
2685
  link_state&.inherited_dependencies || {}
2600
- ).merge(required[:dependencies] || {})
2601
- required[:unmet_dependencies] = (
2602
- required[:unmet_dependencies] || []
2686
+ ).merge(required_code[:dependencies] || {})
2687
+
2688
+ required_code[:unmet_dependencies] = (
2689
+ required_code[:unmet_dependencies] || []
2603
2690
  ) - (link_state&.inherited_block_names || [])
2604
- if required[:unmet_dependencies].present?
2691
+
2692
+ if required_code[:unmet_dependencies].present?
2605
2693
  ### filter against link_state.inherited_block_names
2606
2694
 
2607
2695
  warn format_and_highlight_dependencies(
2608
- dependencies, highlight: required[:unmet_dependencies]
2696
+ dependencies, highlight: required_code[:unmet_dependencies]
2609
2697
  )
2610
2698
  runtime_exception(
2611
2699
  :runtime_exception_error_level,
2612
2700
  'unmet_dependencies, flag: runtime_exception_error_level',
2613
- required[:unmet_dependencies]
2701
+ required_code[:unmet_dependencies]
2614
2702
  )
2615
2703
  elsif @delegate_object[:dump_dependencies]
2616
2704
  warn format_and_highlight_dependencies(
@@ -2621,43 +2709,55 @@ module MarkdownExec
2621
2709
 
2622
2710
  if selected[:type] == BlockType::OPTS
2623
2711
  # body of blocks is returned as a list of lines to be read an YAML
2624
- HashDelegator.flatten_and_compact_arrays(
2625
- required[:blocks].map(&:body).flatten(1)
2712
+ OpenStruct.new(
2713
+ context_code: HashDelegator.flatten_and_compact_arrays(
2714
+ required_code[:blocks].map(&:body).flatten(1)
2715
+ ),
2716
+ transient_code: []
2626
2717
  )
2627
2718
  else
2628
2719
  # code from the selected VARS block
2629
2720
  vars_code = if selected.type == BlockType::VARS
2630
- code_from_vars_block_to_set_environment_variables(selected)
2631
- else
2632
- []
2633
- end
2721
+ code_from_vars_block_to_set_environment_variables(selected)
2722
+ else
2723
+ []
2724
+ end
2634
2725
 
2635
- # activate UX blocks in the required list
2726
+ # activate UX blocks in the required_code list
2636
2727
  command_result_w_e_t_nl = code_from_ux_block_to_set_environment_variables(
2637
2728
  selected,
2638
2729
  @dml_mdoc,
2639
- inherited_code: vars_code,
2730
+ context_code: required_code[:context_code] + required_code[:transient_code],
2640
2731
  only_default: false,
2641
- required: required,
2732
+ required:
2733
+ OpenStruct.new(context_code: [], transient_code: vars_code),
2642
2734
  silent: false
2643
2735
  )
2644
2736
  ux_code = command_result_w_e_t_nl.failure? ? [] : command_result_w_e_t_nl.new_lines
2645
2737
 
2646
- HashDelegator.flatten_and_compact_arrays(
2647
- link_state&.inherited_lines,
2648
- annotate_required_lines(
2649
- 'blk:PORT',
2650
- required[:code] + vars_code + ux_code,
2651
- block_name: selected.id
2738
+ OpenStruct.new(
2739
+ context_code: HashDelegator.flatten_and_compact_arrays(
2740
+ link_state&.context_code,
2741
+ annotate_required_lines(
2742
+ 'blk:PORT',
2743
+ (required_code[:context_code] || []) + vars_code + ux_code,
2744
+ block_name: selected.id
2745
+ )
2746
+ ),
2747
+ transient_code: HashDelegator.flatten_and_compact_arrays(
2748
+ annotate_required_lines(
2749
+ 'blk:PORT',
2750
+ required_code[:transient_code],
2751
+ block_name: selected.id
2752
+ )
2652
2753
  )
2653
2754
  )
2654
2755
  end
2655
-
2656
2756
  rescue StandardError
2657
- wwe(required[:code], ux_code, { error: $!, callback: $@[0] })
2757
+ wwe(required_code, ux_code, { error: $!, callback: $@[0] })
2658
2758
  end
2659
2759
 
2660
- def execute_block_type_save(code_lines:, selected:)
2760
+ def execute_block_type_save(transient_code:, selected:)
2661
2761
  block_data = HashDelegator.parse_yaml_data_from_body(selected.body)
2662
2762
  directory_glob = if block_data['directory']
2663
2763
  File.join(
@@ -2678,7 +2778,7 @@ module MarkdownExec
2678
2778
  end
2679
2779
 
2680
2780
  File.write(save_filespec,
2681
- HashDelegator.join_code_lines(code_lines))
2781
+ HashDelegator.join_code_lines(transient_code))
2682
2782
  rescue Errno::ENOENT
2683
2783
  report_error($ERROR_INFO)
2684
2784
  end
@@ -2773,14 +2873,14 @@ module MarkdownExec
2773
2873
  end
2774
2874
 
2775
2875
  def execute_inherited_save(
2776
- code_lines: @dml_link_state.inherited_lines
2876
+ transient_code: @dml_link_state.context_code
2777
2877
  )
2778
2878
  return unless (save_filespec = save_filespec_from_expression(
2779
2879
  document_name_in_glob_as_file_name
2780
2880
  ))
2781
2881
 
2782
2882
  unless write_file_with_directory_creation(
2783
- content: HashDelegator.join_code_lines(code_lines),
2883
+ content: HashDelegator.join_code_lines(transient_code),
2784
2884
  filespec: save_filespec
2785
2885
  )
2786
2886
  :break
@@ -2791,14 +2891,14 @@ module MarkdownExec
2791
2891
  @menu_user_clicked_back_link = true
2792
2892
 
2793
2893
  keep_code = @dml_link_state.keep_code
2794
- inherited_lines = keep_code ? @dml_link_state.inherited_lines_block : nil
2894
+ context_code = keep_code ? @dml_link_state.context_code_block : nil
2795
2895
 
2796
2896
  @dml_link_state = pop_link_history_new_state
2797
2897
 
2798
2898
  {
2799
2899
  block_name: @dml_link_state.block_name,
2800
2900
  document_filename: @dml_link_state.document_filename,
2801
- inherited_lines: inherited_lines,
2901
+ context_code: context_code,
2802
2902
  keep_code: keep_code
2803
2903
  }
2804
2904
  end
@@ -2814,23 +2914,25 @@ module MarkdownExec
2814
2914
  def execute_required_lines(
2815
2915
  blockname: '',
2816
2916
  erls: {},
2817
- required_lines: [],
2917
+ script_lines: [],
2818
2918
  shell:
2819
2919
  )
2820
2920
  if @delegate_object[:save_executed_script]
2821
2921
  write_command_file(blockname: blockname,
2822
- required_lines: required_lines,
2922
+ required_lines: script_lines,
2823
2923
  shell: selected_shell(shell))
2824
2924
  end
2825
2925
  if @dml_block_state
2826
2926
  calc_logged_stdout_filename(block_name: @dml_block_state.block.oname)
2827
2927
  end
2828
2928
  format_and_execute_command(
2829
- code_lines: required_lines,
2929
+ script_lines: script_lines,
2830
2930
  erls: erls,
2831
2931
  shell: selected_shell(shell)
2832
2932
  )
2833
2933
  post_execution_process
2934
+ rescue StandardError
2935
+ wwe $!
2834
2936
  end
2835
2937
 
2836
2938
  def expand_blocks_with_replacements(
@@ -2926,7 +3028,8 @@ module MarkdownExec
2926
3028
  end
2927
3029
 
2928
3030
  def ux_block_eval_for_export(
2929
- bash_script_lines, export,
3031
+ bash_script_lines,
3032
+ export,
2930
3033
  data:,
2931
3034
  first_only: false,
2932
3035
  force:,
@@ -2934,68 +3037,90 @@ module MarkdownExec
2934
3037
  silent:,
2935
3038
  string: nil
2936
3039
  )
2937
- exportable = true
2938
3040
  command_result = nil
2939
3041
  new_lines = []
3042
+ required_lines = []
2940
3043
  export_string = string.nil? ? data : string
2941
3044
  expander = ->(expression) { %(printf '%s' "#{expression}") }
3045
+ exportable = true
2942
3046
 
2943
3047
  case export_string
2944
3048
  when String, Integer, Float, TrueClass, FalseClass
2945
- command_result, exportable, = output_from_adhoc_bash_script_file(
2946
- join_array_of_arrays(
3049
+ command_result = HashDelegator.execute_bash_script_lines(
3050
+ transient_code: join_array_of_arrays(
2947
3051
  bash_script_lines,
2948
3052
  printf_expand ? expander.call(export_string) : [export_string]
2949
3053
  ),
2950
- export,
2951
- force: force
3054
+ export: export,
3055
+ export_name: export.name,
3056
+ force: force,
3057
+ shell: @delegate_object[:shell],
3058
+ archive_script: @delegate_object[:archive_ad_hoc_scripts],
3059
+ archive_path_format: @delegate_object[:archive_path_format],
3060
+ archive_time_format: @delegate_object[:archive_time_format]
2952
3061
  )
2953
- if command_result.exit_status == EXIT_STATUS_REQUIRED_EMPTY
3062
+ exportable = command_result&.exportable
3063
+
3064
+ if command_result&.exit_status == CommandResult::EXIT_STATUS_REQUIRED_EMPTY
2954
3065
  exportable = false
2955
3066
  unless silent
2956
3067
  command_result.warning = warning_required_empty(export)
2957
3068
  end
3069
+ elsif command_result.nil? || command_result.failure?
3070
+ exportable = false
3071
+ unless silent
3072
+ command_result.warning = warning_failure(export)
3073
+ end
2958
3074
  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 }
3075
+ out = command_result.stdout.to_s
3076
+ if export_environment_variable(export.name, out)
3077
+ new_lines << {
3078
+ name: export.name, force: force, text: out
3079
+ }
3080
+ required_lines << code_line_to_assign_a_variable(
3081
+ export.name, out, force: force
3082
+ )
3083
+ end
2964
3084
  end
2965
3085
 
2966
3086
  when Hash
2967
- required_lines = []
2968
3087
 
2969
3088
  # each item in the hash is a variable name and value
2970
3089
  export_string.each do |name, expression|
2971
- command_result, = output_from_adhoc_bash_script_file(
2972
- join_array_of_arrays(
3090
+ command_result = HashDelegator.execute_bash_script_lines(
3091
+ transient_code: join_array_of_arrays(
2973
3092
  bash_script_lines,
2974
3093
  required_lines,
2975
3094
  printf_expand ? expander.call(expression) : [expression]
2976
3095
  ),
2977
- export,
2978
- force: force
2979
- )
2980
- if command_result.exit_status == EXIT_STATUS_REQUIRED_EMPTY
3096
+ export: export,
3097
+ export_name: name,
3098
+ force: force,
3099
+ shell: @delegate_object[:shell],
3100
+ archive_script: @delegate_object[:archive_ad_hoc_scripts],
3101
+ archive_path_format: @delegate_object[:archive_path_format],
3102
+ archive_time_format: @delegate_object[:archive_time_format]
3103
+ ).tap { ww _1 }
3104
+ if command_result.exit_status == CommandResult::EXIT_STATUS_REQUIRED_EMPTY
3105
+ exportable = false
2981
3106
  unless silent
2982
3107
  command_result.warning = warning_required_empty(export)
2983
3108
  end
3109
+ elsif command_result.failure?
3110
+ exportable = false
3111
+ unless silent
3112
+ command_result.warning = warning_failure(export)
3113
+ end
2984
3114
  else
2985
- transformed = command_result.stdout.to_s
2986
-
2987
- # code for subsequent expression evaluations
3115
+ out = command_result.stdout.to_s
3116
+ if export_environment_variable(name, out)
3117
+ new_lines << {
3118
+ name: name, force: force, text: out
3119
+ }
3120
+ end
2988
3121
  required_lines << code_line_to_assign_a_variable(
2989
- name, transformed, force: force
3122
+ name, out, force: force
2990
3123
  )
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
3124
  end
3000
3125
 
3001
3126
  break if first_only
@@ -3004,9 +3129,23 @@ module MarkdownExec
3004
3129
  # do nothing
3005
3130
  end
3006
3131
 
3007
- [command_result, exportable, new_lines]
3132
+ if command_result
3133
+ command_result.exportable = exportable
3134
+ command_result.new_lines = new_lines
3135
+ command_result.required_lines = required_lines
3136
+ end
3137
+
3138
+ command_result
3008
3139
  rescue StandardError
3009
- wwe bash_script_lines, export, force, silent, string
3140
+ wwe $!, bash_script_lines, export, force, silent, string
3141
+ end
3142
+
3143
+ def export_environment_variable(export_name, stdout)
3144
+ return unless variable_is_exportable(export_name)
3145
+
3146
+ EnvInterface.set(export_name, stdout)
3147
+
3148
+ true
3010
3149
  end
3011
3150
 
3012
3151
  # Retrieves a specific data symbol from the delegate object,
@@ -3080,11 +3219,11 @@ module MarkdownExec
3080
3219
  end
3081
3220
 
3082
3221
  def format_and_execute_command(
3083
- code_lines:,
3222
+ script_lines:,
3084
3223
  erls:,
3085
3224
  shell:
3086
3225
  )
3087
- formatted_command = code_lines.flatten.join("\n")
3226
+ formatted_command = script_lines.flatten.join("\n")
3088
3227
  @fout.fout fetch_color(data_sym: :script_execution_head,
3089
3228
  color_sym: :script_execution_frame_color)
3090
3229
 
@@ -3353,7 +3492,7 @@ module MarkdownExec
3353
3492
  wwt :iterlines, 'nested_line:', nested_line
3354
3493
  update_line_and_block_state(
3355
3494
  nested_line, state, selected_types,
3356
- source_id: "ItrBlkFrmNstFls:#{index}¤#{nested_line.filename}:#{nested_line.index}",
3495
+ source_id: "IBNF:#{index}¤#{nested_line.filename}:#{nested_line.index}",
3357
3496
  &block
3358
3497
  )
3359
3498
 
@@ -3393,10 +3532,10 @@ module MarkdownExec
3393
3532
  end.compact.flatten(1)
3394
3533
  end
3395
3534
 
3396
- def link_block_data_eval(link_state, code_lines, selected, link_block_data,
3535
+ def link_block_data_eval(link_state, transient_code, selected, link_block_data,
3397
3536
  block_source:, shell:)
3398
3537
  all_code = HashDelegator.flatten_and_compact_arrays(
3399
- link_state&.inherited_lines, code_lines
3538
+ link_state&.context_code, transient_code
3400
3539
  )
3401
3540
  output_lines = []
3402
3541
 
@@ -3415,34 +3554,34 @@ module MarkdownExec
3415
3554
  output_lines.push(line) if line
3416
3555
  end
3417
3556
 
3418
- if link_block_data.fetch(LinkKeys::EVAL, true)
3419
- # eval: true
3420
-
3421
- ## select output_lines that look like assignment or match other specs
3422
- #
3423
- output_lines = process_string_array(
3424
- output_lines,
3425
- begin_pattern: @delegate_object.fetch(:output_assignment_begin,
3426
- nil),
3427
- end_pattern: @delegate_object.fetch(:output_assignment_end, nil),
3428
- scan1: @delegate_object.fetch(:output_assignment_match, nil),
3429
- format1: @delegate_object.fetch(:output_assignment_format, nil),
3430
- name: ''
3431
- )
3557
+ output_lines = if link_block_data.fetch(LinkKeys::EVAL, true)
3558
+ # eval: true
3559
+
3560
+ ## select output_lines that look like assignment or match other specs
3561
+ #
3562
+ process_string_array(
3563
+ output_lines,
3564
+ begin_pattern: @delegate_object.fetch(:output_assignment_begin,
3565
+ nil),
3566
+ end_pattern: @delegate_object.fetch(:output_assignment_end, nil),
3567
+ scan1: @delegate_object.fetch(:output_assignment_match, nil),
3568
+ format1: @delegate_object.fetch(:output_assignment_format, nil),
3569
+ name: ''
3570
+ )
3432
3571
 
3433
- else
3434
- # eval: false
3435
-
3436
- ## select all output_lines
3437
- #
3438
- output_lines = process_string_array(
3439
- output_lines,
3440
- begin_pattern: @delegate_object.fetch(:output_assignment_begin,
3441
- nil),
3442
- end_pattern: @delegate_object.fetch(:output_assignment_end, nil),
3443
- name: ''
3444
- )
3445
- end
3572
+ else
3573
+ # eval: false
3574
+
3575
+ ## select all output_lines
3576
+ #
3577
+ process_string_array(
3578
+ output_lines,
3579
+ begin_pattern: @delegate_object.fetch(:output_assignment_begin,
3580
+ nil),
3581
+ end_pattern: @delegate_object.fetch(:output_assignment_end, nil),
3582
+ name: ''
3583
+ )
3584
+ end
3446
3585
  else
3447
3586
  output_lines = `bash #{file.path}`.split("\n")
3448
3587
  end
@@ -3489,7 +3628,7 @@ module MarkdownExec
3489
3628
 
3490
3629
  def link_history_push_and_next(
3491
3630
  curr_block_name:, curr_document_filename:,
3492
- inherited_block_names:, inherited_dependencies:, inherited_lines:,
3631
+ inherited_block_names:, inherited_dependencies:, context_code:,
3493
3632
  keep_code:,
3494
3633
  next_block_name:, next_document_filename:,
3495
3634
  next_keep_code:,
@@ -3501,7 +3640,7 @@ module MarkdownExec
3501
3640
  document_filename: curr_document_filename,
3502
3641
  inherited_block_names: inherited_block_names,
3503
3642
  inherited_dependencies: inherited_dependencies,
3504
- inherited_lines: inherited_lines,
3643
+ context_code: context_code,
3505
3644
  keep_code: keep_code
3506
3645
  )
3507
3646
  )
@@ -3512,7 +3651,7 @@ module MarkdownExec
3512
3651
  document_filename: next_document_filename,
3513
3652
  inherited_block_names: inherited_block_names,
3514
3653
  inherited_dependencies: inherited_dependencies,
3515
- inherited_lines: inherited_lines,
3654
+ context_code: context_code,
3516
3655
  keep_code: next_keep_code
3517
3656
  )
3518
3657
  )
@@ -3702,21 +3841,27 @@ module MarkdownExec
3702
3841
  reload_blocks = true
3703
3842
  end
3704
3843
 
3844
+ link_state_context_code = link_state&.context_code || []
3845
+
3705
3846
  # return code resulting from evaluating all SHELL, UX, VARS blocks;
3706
3847
  # each set in sequence; with its own order
3707
3848
  #
3708
- if (code_lines = code_from_auto_blocks(
3849
+ if (transient_code = inherited_lines_from_auto_blocks(
3709
3850
  all_blocks,
3710
3851
  default_only: true,
3711
- mdoc: mdoc
3852
+ mdoc: mdoc,
3853
+ context_code: link_state_context_code
3712
3854
  ))&.select_by(:empty?, false)&.compact&.count&.positive?
3713
- new_code = code_lines
3714
- wwt :code_lines, 'code_lines:', code_lines
3855
+ wwt :transient_code, 'transient_code:', transient_code
3856
+
3857
+ # prepend context code to new transient code
3858
+ new_code = link_state_context_code + transient_code
3715
3859
  next_state_set_code(nil, link_state, new_code)
3716
- link_state.inherited_lines = new_code
3860
+ link_state.context_code = new_code
3861
+
3717
3862
  reload_blocks = true
3718
3863
  else
3719
- link_state&.inherited_lines
3864
+ link_state&.context_code
3720
3865
  end
3721
3866
 
3722
3867
  if reload_blocks
@@ -3876,6 +4021,14 @@ module MarkdownExec
3876
4021
  end
3877
4022
  end
3878
4023
 
4024
+ # Define respond_to_missing? to match method_missing behavior
4025
+ def respond_to_missing?(method_name, include_private = false)
4026
+ return true if @delegate_object.respond_to?(method_name, include_private)
4027
+ return true if method_name.to_s.end_with?('=')
4028
+
4029
+ true # Any other method name is treated as a key accessor
4030
+ end
4031
+
3879
4032
  # If a method is missing, treat it as a key for the @delegate_object.
3880
4033
  def method_missing(method_name, *args, &block)
3881
4034
  if @delegate_object.respond_to?(method_name)
@@ -3895,19 +4048,19 @@ module MarkdownExec
3895
4048
  list[(index + 1) % list.size] # Get the next item, wrap around if at the end
3896
4049
  end
3897
4050
 
3898
- def next_state_append_code(selected, link_state, code_lines,
4051
+ def next_state_append_code(selected, link_state, context_code:,
3899
4052
  mode: LoadMode::APPEND)
3900
4053
  next_state_set_code(
3901
4054
  selected,
3902
4055
  link_state,
3903
4056
  HashDelegator.flatten_and_compact_arrays(
3904
- mode == LoadMode::APPEND ? link_state&.inherited_lines : [],
3905
- code_lines.is_a?(Array) ? code_lines : [] # no code for :ux_exec_prohibited
4057
+ mode == LoadMode::APPEND ? link_state&.context_code : [],
4058
+ context_code.is_a?(Array) ? context_code : [] # no code for :ux_exec_prohibited
3906
4059
  )
3907
4060
  )
3908
4061
  end
3909
4062
 
3910
- def next_state_set_code(selected, link_state, code_lines)
4063
+ def next_state_set_code(selected, link_state, context_code)
3911
4064
  block_names = []
3912
4065
  dependencies = {}
3913
4066
  link_history_push_and_next(
@@ -3917,7 +4070,7 @@ module MarkdownExec
3917
4070
  ((link_state&.inherited_block_names || []) + block_names).sort.uniq,
3918
4071
  inherited_dependencies:
3919
4072
  (link_state&.inherited_dependencies || {}).merge(dependencies || {}), ### merge, not replace, key data
3920
- inherited_lines: HashDelegator.flatten_and_compact_arrays(code_lines),
4073
+ context_code: HashDelegator.flatten_and_compact_arrays(context_code),
3921
4074
  keep_code: link_state&.keep_code,
3922
4075
  next_block_name: '',
3923
4076
  next_document_filename: @delegate_object[:filename],
@@ -3961,60 +4114,6 @@ module MarkdownExec
3961
4114
  }
3962
4115
  end
3963
4116
 
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
4117
  def output_labeled_value(label, value, level)
4019
4118
  @fout.lout format_references_send_color(
4020
4119
  context: {
@@ -4036,7 +4135,7 @@ module MarkdownExec
4036
4135
  end
4037
4136
 
4038
4137
  def pop_add_current_code_to_head_and_trigger_load(
4039
- link_state, block_names, code_lines,
4138
+ link_state, block_names, transient_code,
4040
4139
  dependencies, selected, next_block_name: nil
4041
4140
  )
4042
4141
  pop = @link_history.pop # updatable
@@ -4048,9 +4147,9 @@ module MarkdownExec
4048
4147
  (pop.inherited_block_names + block_names).sort.uniq,
4049
4148
  inherited_dependencies:
4050
4149
  dependencies.merge(pop.inherited_dependencies || {}), ### merge, not replace, key data
4051
- inherited_lines:
4052
- HashDelegator.flatten_and_compact_arrays(pop.inherited_lines,
4053
- code_lines)
4150
+ context_code:
4151
+ HashDelegator.flatten_and_compact_arrays(pop.context_code,
4152
+ transient_code)
4054
4153
  )
4055
4154
  @link_history.push(next_state)
4056
4155
 
@@ -4066,9 +4165,9 @@ module MarkdownExec
4066
4165
  ((link_state&.inherited_block_names || []) + block_names).sort.uniq,
4067
4166
  inherited_dependencies:
4068
4167
  (link_state&.inherited_dependencies || {}).merge(dependencies || {}), ### merge, not replace, key data
4069
- inherited_lines:
4168
+ context_code:
4070
4169
  HashDelegator.flatten_and_compact_arrays(
4071
- link_state&.inherited_lines, code_lines
4170
+ link_state&.context_code, transient_code
4072
4171
  ),
4073
4172
  keep_code: link_state&.keep_code,
4074
4173
  next_block_name: next_block_name,
@@ -4093,7 +4192,7 @@ module MarkdownExec
4093
4192
  document_filename: pop.document_filename,
4094
4193
  inherited_block_names: peek.inherited_block_names,
4095
4194
  inherited_dependencies: peek.inherited_dependencies,
4096
- inherited_lines: peek.inherited_lines
4195
+ context_code: peek.context_code
4097
4196
  )
4098
4197
  end
4099
4198
 
@@ -4108,7 +4207,7 @@ module MarkdownExec
4108
4207
  blocks.select { |item| item.type == 'ux' }
4109
4208
  end
4110
4209
 
4111
- # Filter blocks per block_name_include_match, block_name_wrapper_match.
4210
+ # Filter blocks per block_name_hidden_match, block_name_wrapper_match.
4112
4211
  #
4113
4212
  # @param all_blocks [Array<Hash>] The list of blocks from the file.
4114
4213
  def select_blocks(menu_blocks)
@@ -4116,12 +4215,12 @@ module MarkdownExec
4116
4215
  Filter.prepared_not_in_menu?(
4117
4216
  @delegate_object,
4118
4217
  fcb,
4119
- %i[block_name_include_match block_name_wrapper_match]
4218
+ %i[block_name_hidden_match block_name_wrapper_match]
4120
4219
  )
4121
4220
  end
4122
4221
  end
4123
4222
 
4124
- # Filter blocks per block_name_include_match, block_name_wrapper_match.
4223
+ # Filter blocks per block_name_hidden_match, block_name_wrapper_match.
4125
4224
  # Set name displayed by tty-prompt.
4126
4225
  #
4127
4226
  # @param all_blocks [Array<Hash>] The list of blocks from the file.
@@ -4167,11 +4266,7 @@ module MarkdownExec
4167
4266
  if name.empty?
4168
4267
  "# #{comment}" unless comment.empty?
4169
4268
  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
4269
+ transformed = name_force[:text]
4175
4270
 
4176
4271
  # store the transformed value in ENV
4177
4272
  EnvInterface.set(name, transformed)
@@ -4473,11 +4568,13 @@ module MarkdownExec
4473
4568
  obj = {}
4474
4569
 
4475
4570
  # concatenated body of all required blocks loaded a YAML
4571
+
4572
+ rc = execute_block_type_port_code_lines(
4573
+ mdoc: mdoc, selected: selected,
4574
+ link_state: link_state, block_source: {}
4575
+ )
4476
4576
  data = (YAML.load(
4477
- execute_block_type_port_code_lines(
4478
- mdoc: mdoc, selected: selected,
4479
- link_state: link_state, block_source: {}
4480
- ).join("\n")
4577
+ rc[:context_code].join("\n")
4481
4578
  ) || {}).transform_keys(&:to_sym)
4482
4579
 
4483
4580
  if selected.type == BlockType::OPTS
@@ -4592,7 +4689,7 @@ module MarkdownExec
4592
4689
  @delegate_object.respond_to?(:[]=, include_private)
4593
4690
  true
4594
4691
  else
4595
- @delegate_object.respond_to?(method_name, include_private)
4692
+ false
4596
4693
  end
4597
4694
  end
4598
4695
 
@@ -4677,8 +4774,8 @@ module MarkdownExec
4677
4774
  filename: filename,
4678
4775
  saved_asset_format:
4679
4776
  shell_escape_asset_format(
4680
- code_lines: link_state&.inherited_lines,
4681
- shell: selected_shell(shell)
4777
+ transient_code: link_state&.context_code,
4778
+ shell: selected_shell(@delegate_object[:shell])
4682
4779
  )
4683
4780
  ).generate_name
4684
4781
  end
@@ -4846,7 +4943,7 @@ module MarkdownExec
4846
4943
  end
4847
4944
 
4848
4945
  def shell_escape_asset_format(
4849
- code_lines:,
4946
+ transient_code:,
4850
4947
  enable: @delegate_object[:shell_parameter_expansion],
4851
4948
  raw: @delegate_object[:saved_asset_format],
4852
4949
  shell:
@@ -4861,7 +4958,7 @@ module MarkdownExec
4861
4958
 
4862
4959
  marker = Random.new.rand.to_s
4863
4960
 
4864
- code = (code_lines || []) + ["echo -n \"#{marker}#{raw}\""]
4961
+ code = (transient_code || []) + ["echo -n \"#{marker}#{raw}\""]
4865
4962
  File.write filespec, HashDelegator.join_code_lines(code)
4866
4963
  File.chmod 0o755, filespec
4867
4964
 
@@ -4966,7 +5063,6 @@ module MarkdownExec
4966
5063
  # color_sym is not found in @delegate_object.
4967
5064
  # @return [String] The string with the applied color method.
4968
5065
  def string_send_color(string, color_sym)
4969
- ### accept string with color as well as symbol for color_hash
4970
5066
  HashDelegator.apply_color_from_hash(string, @delegate_object, color_sym)
4971
5067
  end
4972
5068
 
@@ -5094,21 +5190,20 @@ module MarkdownExec
5094
5190
  force = export.force.nil? ? true : export.force
5095
5191
  new_lines = []
5096
5192
  silent = false
5097
- transformable = true
5098
5193
 
5099
5194
  case FCB.act_source(export)
5100
5195
  when false, UxActSource::FALSE
5101
5196
  # read-only
5102
- command_result = CommandResult.new
5103
- exportable = false
5104
- transformable = false
5197
+ command_result = CommandResult.new(
5198
+ exportable: false
5199
+ )
5105
5200
 
5106
5201
  when :allow, UxActSource::ALLOW
5107
5202
  raise unless export.allow.present?
5108
5203
 
5109
5204
  case export.allow
5110
5205
  when :echo, ExportValueSource::ECHO
5111
- command_result, exportable, new_lines = ux_block_eval_for_export(
5206
+ command_result = ux_block_eval_for_export(
5112
5207
  bash_script_lines,
5113
5208
  export,
5114
5209
  data: export.echo,
@@ -5118,18 +5213,15 @@ module MarkdownExec
5118
5213
  silent: silent
5119
5214
  )
5120
5215
 
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
- )
5216
+ if command_result.success?
5217
+ command_result.exportable = true
5218
+ command_result.stdout = menu_from_list_with_back(
5219
+ com_exp_cod.command_result.stdout.split("\n")
5128
5220
  )
5129
5221
  end
5130
5222
 
5131
5223
  when :exec, UxActSource::EXEC
5132
- command_result, exportable, new_lines = ux_block_eval_for_export(
5224
+ command_result = ux_block_eval_for_export(
5133
5225
  bash_script_lines,
5134
5226
  export,
5135
5227
  data: export.exec,
@@ -5138,19 +5230,19 @@ module MarkdownExec
5138
5230
  silent: silent
5139
5231
  )
5140
5232
 
5141
- if command_result.exit_status == EXIT_STATUS_REQUIRED_EMPTY
5233
+ if command_result.exit_status ==
5234
+ CommandResult::EXIT_STATUS_REQUIRED_EMPTY
5142
5235
  command_result
5143
5236
  else
5144
- command_result = CommandResult.new(
5145
- stdout: menu_from_list_with_back(
5146
- command_result.stdout.split("\n")
5147
- )
5237
+ command_result.exportable = true
5238
+ command_result.stdout = menu_from_list_with_back(
5239
+ command_result.stdout.split("\n")
5148
5240
  )
5149
5241
  end
5150
5242
 
5151
5243
  else
5152
5244
  export_init = menu_from_list_with_back(export.allow)
5153
- command_result, exportable, new_lines = ux_block_eval_for_export(
5245
+ command_result = ux_block_eval_for_export(
5154
5246
  [assign_key_value_in_bash(export.name, export_init)],
5155
5247
  export,
5156
5248
  data: export.echo,
@@ -5160,11 +5252,13 @@ module MarkdownExec
5160
5252
  silent: silent,
5161
5253
  string: export_init
5162
5254
  )
5163
-
5255
+ if command_result.success?
5256
+ command_result.exportable = true
5257
+ end
5164
5258
  end
5165
5259
 
5166
5260
  when :echo, UxActSource::ECHO
5167
- command_result, exportable, new_lines = ux_block_eval_for_export(
5261
+ command_result = ux_block_eval_for_export(
5168
5262
  bash_script_lines,
5169
5263
  export,
5170
5264
  data: export.echo,
@@ -5172,6 +5266,9 @@ module MarkdownExec
5172
5266
  printf_expand: true,
5173
5267
  silent: silent
5174
5268
  )
5269
+ if command_result.success?
5270
+ command_result.exportable = true
5271
+ end
5175
5272
 
5176
5273
  when :edit, UxActSource::EDIT
5177
5274
  output = ''
@@ -5188,7 +5285,6 @@ module MarkdownExec
5188
5285
  end
5189
5286
  rescue Interrupt
5190
5287
  exportable = false
5191
- transformable = false
5192
5288
  end
5193
5289
 
5194
5290
  if exportable
@@ -5197,29 +5293,35 @@ module MarkdownExec
5197
5293
  text: output }
5198
5294
  end
5199
5295
  command_result = CommandResult.new(stdout: output)
5296
+ if command_result.success?
5297
+ command_result.exportable = true
5298
+ end
5200
5299
 
5201
5300
  when :exec, UxActSource::EXEC
5202
- command_result, exportable, new_lines = ux_block_eval_for_export(
5301
+ command_result = ux_block_eval_for_export(
5203
5302
  bash_script_lines,
5204
5303
  export,
5205
5304
  data: export.exec,
5206
5305
  force: force,
5207
5306
  silent: silent
5208
5307
  )
5308
+ if command_result.success?
5309
+ command_result.exportable = true
5310
+ end
5209
5311
 
5210
5312
  else
5211
- transformable = false
5212
5313
  command_result = CommandResult.new(stdout: export.default.to_s)
5314
+ if command_result.success?
5315
+ command_result.exportable = true
5316
+ end
5213
5317
  end
5214
5318
 
5215
5319
  # add message for required variables
5216
- if command_result.exit_status == EXIT_STATUS_REQUIRED_EMPTY
5320
+ if command_result.exit_status == CommandResult::EXIT_STATUS_REQUIRED_EMPTY
5217
5321
  command_result.warning = warning_required_empty(export)
5218
5322
  warn command_result.warning unless silent
5219
5323
  end
5220
5324
 
5221
- command_result.exportable = exportable
5222
- command_result.transformable = transformable
5223
5325
  command_result.new_lines = new_lines
5224
5326
  command_result
5225
5327
  rescue StandardError
@@ -5232,22 +5334,21 @@ module MarkdownExec
5232
5334
  command_result = nil
5233
5335
  exportable = true
5234
5336
  force = export.force.nil? ? false : export.force
5235
- new_lines = []
5236
5337
  silent = true
5237
- transformable = true
5238
5338
 
5239
5339
  case FCB.init_source(export)
5240
5340
  when false, UxActSource::FALSE
5241
5341
  exportable = false
5242
- transformable = false
5243
- command_result = CommandResult.new
5342
+ command_result = CommandResult.new(
5343
+ exportable: false
5344
+ )
5244
5345
 
5245
5346
  when :allow, UxActSource::ALLOW
5246
5347
  raise unless export.allow.present?
5247
5348
 
5248
5349
  case export.allow
5249
5350
  when :echo, ExportValueSource::ECHO
5250
- cr_echo, = ux_block_eval_for_export(
5351
+ cr_echo = ux_block_eval_for_export(
5251
5352
  bash_script_lines,
5252
5353
  export,
5253
5354
  data: export.echo,
@@ -5258,7 +5359,7 @@ module MarkdownExec
5258
5359
  )
5259
5360
  export_init = cr_echo.stdout.split("\n").first
5260
5361
 
5261
- command_result, exportable, new_lines = ux_block_eval_for_export(
5362
+ command_result = ux_block_eval_for_export(
5262
5363
  [assign_key_value_in_bash(export.name, export_init)],
5263
5364
  export,
5264
5365
  data: export.echo,
@@ -5271,7 +5372,7 @@ module MarkdownExec
5271
5372
 
5272
5373
  when :exec, ExportValueSource::EXEC
5273
5374
  # extract first line from 'exec' output
5274
- command_result, exportable, new_lines = ux_block_eval_for_export(
5375
+ command_result = ux_block_eval_for_export(
5275
5376
  bash_script_lines,
5276
5377
  export,
5277
5378
  data: export.exec,
@@ -5279,10 +5380,9 @@ module MarkdownExec
5279
5380
  force: force,
5280
5381
  silent: silent
5281
5382
  )
5282
-
5283
5383
  unless command_result.failure?
5284
5384
  export_init = command_result.stdout.split("\n").first
5285
- command_result, exportable, new_lines = ux_block_eval_for_export(
5385
+ command_result = ux_block_eval_for_export(
5286
5386
  [assign_key_value_in_bash(export.name, export_init)],
5287
5387
  export,
5288
5388
  data: export.exec,
@@ -5296,7 +5396,7 @@ module MarkdownExec
5296
5396
  else
5297
5397
  # first item from 'allow' list
5298
5398
  export_init = export.allow.first
5299
- command_result, exportable, new_lines = ux_block_eval_for_export(
5399
+ command_result = ux_block_eval_for_export(
5300
5400
  [assign_key_value_in_bash(export.name, export_init)],
5301
5401
  export,
5302
5402
  data: export.allow,
@@ -5309,13 +5409,14 @@ module MarkdownExec
5309
5409
  end
5310
5410
 
5311
5411
  when :default, UxActSource::DEFAULT
5312
- transformable = false
5313
- command_result = CommandResult.new(stdout: export.default.to_s)
5412
+ command_result = CommandResult.new(
5413
+ stdout: export.default.to_s
5414
+ )
5314
5415
 
5315
5416
  when :echo, UxActSource::ECHO
5316
5417
  raise unless export.echo.present?
5317
5418
 
5318
- command_result, exportable, new_lines = ux_block_eval_for_export(
5419
+ command_result = ux_block_eval_for_export(
5319
5420
  bash_script_lines,
5320
5421
  export,
5321
5422
  data: export.echo,
@@ -5327,7 +5428,7 @@ module MarkdownExec
5327
5428
  when :exec, UxActSource::EXEC
5328
5429
  raise unless export.exec.present?
5329
5430
 
5330
- command_result, exportable, new_lines = ux_block_eval_for_export(
5431
+ command_result = ux_block_eval_for_export(
5331
5432
  bash_script_lines,
5332
5433
  export,
5333
5434
  data: export.exec,
@@ -5337,7 +5438,7 @@ module MarkdownExec
5337
5438
 
5338
5439
  else
5339
5440
  export_init = export.init.to_s
5340
- command_result, exportable, new_lines = ux_block_eval_for_export(
5441
+ command_result = ux_block_eval_for_export(
5341
5442
  [assign_key_value_in_bash(export.name, export_init)],
5342
5443
  export,
5343
5444
  data: export.exec,
@@ -5349,17 +5450,15 @@ module MarkdownExec
5349
5450
  end
5350
5451
 
5351
5452
  # add message for required variables
5352
- if command_result.exit_status == EXIT_STATUS_REQUIRED_EMPTY
5453
+ if command_result.exit_status == CommandResult::EXIT_STATUS_REQUIRED_EMPTY
5353
5454
  command_result.warning = warning_required_empty(export)
5354
5455
  warn command_result.warning unless silent
5355
5456
  end
5356
5457
 
5357
- command_result.exportable = exportable
5358
- command_result.transformable = transformable
5359
- command_result.new_lines = new_lines
5458
+ command_result.exportable = command_result.success? && exportable
5360
5459
  command_result
5361
5460
  rescue StandardError
5362
- wwe bash_script_lines, export
5461
+ wwe $!, bash_script_lines, export
5363
5462
  end
5364
5463
 
5365
5464
  # true if the variable is exported in a series of evaluations
@@ -5395,10 +5494,10 @@ module MarkdownExec
5395
5494
  end
5396
5495
 
5397
5496
  def vux_edit_inherited
5398
- edited = edit_text(@dml_link_state.inherited_lines_block)
5497
+ edited = edit_text(@dml_link_state.context_code_block)
5399
5498
  return unless edited
5400
5499
 
5401
- @dml_link_state.inherited_lines =
5500
+ @dml_link_state.context_code =
5402
5501
  annotate_required_lines(
5403
5502
  'blk:EDIT', edited.split("\n"), block_name: 'EDIT'
5404
5503
  )
@@ -5491,7 +5590,8 @@ module MarkdownExec
5491
5590
 
5492
5591
  elsif block_is_shell(@dml_block_state.block)
5493
5592
  debounce_reset
5494
- vux_input_and_execute_shell_commands(stream: $stderr, shell: shell)
5593
+ vux_input_and_execute_shell_commands(stream: $stderr,
5594
+ shell: @delegate_object[:shell])
5495
5595
  return pause_user_exit ? :break : nil
5496
5596
 
5497
5597
  elsif block_is_view(@dml_block_state.block)
@@ -5617,7 +5717,7 @@ module MarkdownExec
5617
5717
  def vux_load_code_files_into_state
5618
5718
  return unless @menu_base_options[:load_code].present?
5619
5719
 
5620
- @dml_link_state.inherited_lines =
5720
+ @dml_link_state.context_code =
5621
5721
  @menu_base_options[:load_code].split(':').map do |path|
5622
5722
  File.readlines(path, chomp: true)
5623
5723
  end.flatten(1)
@@ -5628,7 +5728,7 @@ module MarkdownExec
5628
5728
 
5629
5729
  pop_add_current_code_to_head_and_trigger_load(
5630
5730
  @dml_link_state, inherited_block_names,
5631
- code_lines, inherited_dependencies, selected
5731
+ transient_code, inherited_dependencies, selected
5632
5732
  )
5633
5733
  end
5634
5734
 
@@ -5637,7 +5737,7 @@ module MarkdownExec
5637
5737
  document_name_in_glob_as_file_name
5638
5738
  ))
5639
5739
 
5640
- @dml_link_state.inherited_lines_append(
5740
+ @dml_link_state.context_code_append(
5641
5741
  File.readlines(filespec, chomp: true)
5642
5742
  )
5643
5743
  end
@@ -5796,7 +5896,7 @@ module MarkdownExec
5796
5896
  files = document_glob ? Dir.glob(document_glob) : []
5797
5897
  @doc_saved_lines_files = files.count.positive? ? files : []
5798
5898
 
5799
- lines_count = @dml_link_state.inherited_lines_count
5899
+ lines_count = @dml_link_state.context_code_count
5800
5900
 
5801
5901
  # add menu items (glob, load, save) and enable selectively
5802
5902
  if files.count.positive? || lines_count.positive?
@@ -5921,7 +6021,7 @@ module MarkdownExec
5921
6021
  end
5922
6022
 
5923
6023
  def vux_view_inherited(stream:)
5924
- stream.puts @dml_link_state.inherited_lines_block
6024
+ stream.puts @dml_link_state.context_code_block
5925
6025
  end
5926
6026
 
5927
6027
  def wait_for_stream_processing
@@ -5992,6 +6092,10 @@ module MarkdownExec
5992
6092
  determine_block_state(selected_option)
5993
6093
  end
5994
6094
 
6095
+ def warning_failure(export)
6096
+ "A value must exist for: #{export.required.join(', ')}"
6097
+ end
6098
+
5995
6099
  def warning_required_empty(export)
5996
6100
  "A value must exist for: #{export.required.join(', ')}"
5997
6101
  end
@@ -6010,7 +6114,7 @@ module MarkdownExec
6010
6114
  prefix: @delegate_object[:saved_script_filename_prefix],
6011
6115
  saved_asset_format:
6012
6116
  shell_escape_asset_format(
6013
- code_lines: @dml_link_state.inherited_lines,
6117
+ transient_code: @dml_link_state.context_code,
6014
6118
  shell: shell
6015
6119
  ),
6016
6120
  time: time_now
@@ -6069,7 +6173,7 @@ module MarkdownExec
6069
6173
  if save_filespec.present?
6070
6174
  File.write(
6071
6175
  save_filespec,
6072
- HashDelegator.join_code_lines(link_state&.inherited_lines)
6176
+ HashDelegator.join_code_lines(link_state&.context_code)
6073
6177
  )
6074
6178
  @delegate_object[:filename]
6075
6179
  else
@@ -6252,7 +6356,7 @@ module MarkdownExec
6252
6356
  LinkState.new(block_name: 'sample_block',
6253
6357
  document_filename: 'sample_file',
6254
6358
  inherited_dependencies: {},
6255
- inherited_lines: ['# ', 'KEY="VALUE"'])
6359
+ context_code: ['# ', 'KEY="VALUE"'])
6256
6360
  )
6257
6361
  assert_equal expected_result,
6258
6362
  @hd.execute_block_type_link_with_state(
@@ -6428,12 +6532,43 @@ module MarkdownExec
6428
6532
  def test_execute_block_type_port_code_lines_with_vars
6429
6533
  YAML.stubs(:load).returns({ 'key' => 'value' })
6430
6534
  @mdoc.stubs(:collect_recursively_required_code)
6431
- .returns({ code: ['code line'] })
6535
+ .returns({ context_code: [], transient_code: ['code line'] })
6432
6536
  result = @hd.execute_block_type_port_code_lines(
6433
6537
  mdoc: @mdoc, selected: @selected, block_source: {}
6434
6538
  )
6435
6539
 
6436
- assert_equal ['code line', 'key="value"'], result
6540
+ assert_equal ['key="value"'], result[:context_code]
6541
+ assert_equal ['code line'], result[:transient_code]
6542
+ end
6543
+
6544
+ def test_transient_code_parameter_usage
6545
+ # Test that transient_code parameter is used correctly in execute_bash_script_lines
6546
+ transient_code = ["echo 'test'", "echo 'transient'"]
6547
+ export = OpenStruct.new(exportable: false, validate: //, transform: ->(x) { x })
6548
+ result = HashDelegator.execute_bash_script_lines(
6549
+ transient_code: transient_code,
6550
+ export: export,
6551
+ export_name: 'TEST',
6552
+ shell: '/bin/bash'
6553
+ )
6554
+
6555
+ assert result.is_a?(CommandResult)
6556
+ assert_equal transient_code, result.script
6557
+ end
6558
+
6559
+ def test_transient_code_vs_context_code_separation
6560
+ # Test that transient_code is separate from context_code
6561
+ context_code = ['CONTEXT_VAR="context_value"']
6562
+ transient_code = ['TRANSIENT_VAR="transient_value"']
6563
+
6564
+ # Simulate combining them
6565
+ all_code = HashDelegator.flatten_and_compact_arrays(
6566
+ context_code, transient_code
6567
+ )
6568
+
6569
+ assert_equal 2, all_code.length
6570
+ assert_includes all_code, 'CONTEXT_VAR="context_value"'
6571
+ assert_includes all_code, 'TRANSIENT_VAR="transient_value"'
6437
6572
  end
6438
6573
  end
6439
6574
 
@@ -7211,12 +7346,16 @@ module MarkdownExec
7211
7346
  def setup
7212
7347
  @hd = HashDelegator.new
7213
7348
  @bash_script_lines = ['#!/bin/bash', 'set -e']
7214
- @export = OpenStruct.new(name: 'TEST_VAR', required: ['TEST_VAR'])
7349
+ @export = OpenStruct.new(
7350
+ name: 'TEST_VAR', required: ['TEST_VAR'], init: 'default'
7351
+ )
7215
7352
  @mock_result = OpenStruct.new(
7216
- exit_status: 0,
7217
- stdout: 'test_output',
7218
- stderr: '',
7219
- warning: nil
7353
+ command_result: OpenStruct.new(
7354
+ exit_status: 0,
7355
+ stdout: 'test_output',
7356
+ stderr: '',
7357
+ warning: nil
7358
+ )
7220
7359
  )
7221
7360
  end
7222
7361
 
@@ -7229,29 +7368,27 @@ module MarkdownExec
7229
7368
 
7230
7369
  # Test string input - typical case
7231
7370
  def test_string_input_success
7232
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7233
- true])
7234
7371
  @hd.stubs(:variable_is_exportable).returns(true)
7235
7372
 
7236
- result = @hd.ux_block_eval_for_export(
7237
- @bash_script_lines, @export,
7373
+ command_result = @hd.ux_block_eval_for_export(
7374
+ @bash_script_lines,
7375
+ @export,
7238
7376
  data: 'echo "hello"',
7239
7377
  force: false,
7240
7378
  silent: false
7241
7379
  )
7242
7380
 
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]
7381
+ # assert_equal @mock_result, command_result
7382
+ assert_equal 1, command_result.new_lines.length
7383
+ assert_equal 'TEST_VAR', command_result.new_lines.first[:name]
7384
+ assert_equal "hello\n", command_result.stdout
7249
7385
  end
7250
7386
 
7251
7387
  # Test string input with printf_expand
7252
7388
  def test_string_input_with_printf_expand
7253
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7254
- true])
7389
+ HashDelegator.stubs(:execute_bash_script_lines).returns(
7390
+ @mock_result
7391
+ )
7255
7392
  @hd.stubs(:variable_is_exportable).returns(true)
7256
7393
 
7257
7394
  @hd.ux_block_eval_for_export(
@@ -7268,198 +7405,180 @@ module MarkdownExec
7268
7405
 
7269
7406
  # Test integer input
7270
7407
  def test_integer_input
7271
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7272
- true])
7408
+ @mock_result.exportable = true
7409
+ HashDelegator.stubs(:execute_bash_script_lines).returns(
7410
+ @mock_result
7411
+ )
7273
7412
  @hd.stubs(:variable_is_exportable).returns(true)
7274
7413
 
7275
- result = @hd.ux_block_eval_for_export(
7414
+ command_result = @hd.ux_block_eval_for_export(
7276
7415
  @bash_script_lines, @export,
7277
7416
  data: 42,
7278
7417
  force: true,
7279
7418
  silent: false
7280
7419
  )
7281
7420
 
7282
- command_result, exportable, new_lines = result
7283
7421
  assert_equal @mock_result, command_result
7284
- assert_equal true, exportable
7285
- assert_equal 1, new_lines.length
7422
+ assert_equal 1, command_result.new_lines.length
7286
7423
  end
7287
7424
 
7288
7425
  # Test boolean inputs
7289
7426
  def test_boolean_true_input
7290
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7291
- true])
7427
+ @mock_result.exportable = true
7428
+ HashDelegator.stubs(:execute_bash_script_lines).returns(
7429
+ @mock_result
7430
+ )
7292
7431
  @hd.stubs(:variable_is_exportable).returns(true)
7293
7432
 
7294
- result = @hd.ux_block_eval_for_export(
7433
+ command_result = @hd.ux_block_eval_for_export(
7295
7434
  @bash_script_lines, @export,
7296
7435
  data: true,
7297
7436
  force: false,
7298
7437
  silent: false
7299
7438
  )
7300
7439
 
7301
- command_result, exportable, = result
7302
7440
  assert_equal @mock_result, command_result
7303
- assert_equal true, exportable
7304
7441
  end
7305
7442
 
7306
7443
  def test_boolean_false_input
7307
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7308
- true])
7444
+ @mock_result.exportable = true
7445
+ HashDelegator.stubs(:execute_bash_script_lines).returns(
7446
+ @mock_result
7447
+ )
7309
7448
  @hd.stubs(:variable_is_exportable).returns(true)
7310
7449
 
7311
- result = @hd.ux_block_eval_for_export(
7450
+ command_result = @hd.ux_block_eval_for_export(
7312
7451
  @bash_script_lines, @export,
7313
7452
  data: false,
7314
7453
  force: false,
7315
7454
  silent: false
7316
7455
  )
7317
7456
 
7318
- command_result, exportable, = result
7319
7457
  assert_equal @mock_result, command_result
7320
- assert_equal true, exportable
7321
7458
  end
7322
7459
 
7323
7460
  # Test float input
7324
7461
  def test_float_input
7325
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7326
- true])
7462
+ @mock_result.exportable = true
7463
+ HashDelegator.stubs(:execute_bash_script_lines).returns(
7464
+ @mock_result
7465
+ )
7327
7466
  @hd.stubs(:variable_is_exportable).returns(true)
7328
7467
 
7329
- result = @hd.ux_block_eval_for_export(
7468
+ command_result = @hd.ux_block_eval_for_export(
7330
7469
  @bash_script_lines, @export,
7331
7470
  data: 3.14,
7332
7471
  force: false,
7333
7472
  silent: false
7334
7473
  )
7335
7474
 
7336
- command_result, exportable, = result
7337
7475
  assert_equal @mock_result, command_result
7338
- assert_equal true, exportable
7339
7476
  end
7340
7477
 
7341
7478
  # Test hash input - typical case
7342
7479
  def test_hash_input_success
7343
7480
  hash_data = { 'VAR1' => 'value1', 'VAR2' => 'value2' }
7344
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7345
- true])
7481
+ @mock_result.exportable = true
7482
+ HashDelegator.stubs(:execute_bash_script_lines).returns(
7483
+ @mock_result
7484
+ )
7346
7485
  @hd.stubs(:variable_is_exportable).returns(true)
7347
7486
  @hd.stubs(:code_line_to_assign_a_variable).returns('VAR1=value1')
7348
7487
 
7349
- result = @hd.ux_block_eval_for_export(
7488
+ command_result = @hd.ux_block_eval_for_export(
7350
7489
  @bash_script_lines, @export,
7351
7490
  data: hash_data,
7352
7491
  force: false,
7353
7492
  silent: false
7354
7493
  )
7355
7494
 
7356
- command_result, exportable, new_lines = result
7357
7495
  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]
7496
+ assert_equal 2, command_result.new_lines.length
7497
+ assert_equal 'VAR1', command_result.new_lines.first[:name]
7498
+ assert_equal 'VAR2', command_result.new_lines.last[:name]
7362
7499
  end
7363
7500
 
7364
7501
  # Test hash input with first_only flag
7365
7502
  def test_hash_input_first_only
7366
7503
  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
7504
 
7372
- result = @hd.ux_block_eval_for_export(
7505
+ command_result = @hd.ux_block_eval_for_export(
7373
7506
  @bash_script_lines, @export,
7374
7507
  data: hash_data,
7375
7508
  first_only: true,
7376
7509
  force: false,
7510
+ printf_expand: true,
7377
7511
  silent: false
7378
7512
  )
7379
7513
 
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]
7514
+ assert_equal 1, command_result.new_lines.length
7515
+ assert_equal 'VAR1', command_result.new_lines.first[:name]
7384
7516
  end
7385
7517
 
7386
7518
  # Test hash with non-exportable variables
7387
7519
  def test_hash_input_non_exportable_variables
7388
7520
  hash_data = { 'LOCAL_VAR' => 'value1' }
7389
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7390
- true])
7521
+ @mock_result.exportable = true
7522
+ @mock_result.new_lines = []
7523
+ HashDelegator.stubs(:execute_bash_script_lines).returns(@mock_result)
7391
7524
  @hd.stubs(:variable_is_exportable).returns(false)
7392
7525
  @hd.stubs(:code_line_to_assign_a_variable).returns('LOCAL_VAR=value1')
7393
7526
 
7394
- result = @hd.ux_block_eval_for_export(
7527
+ command_result = @hd.ux_block_eval_for_export(
7395
7528
  @bash_script_lines, @export,
7396
7529
  data: hash_data,
7397
7530
  force: false,
7398
7531
  silent: false
7399
7532
  )
7400
7533
 
7401
- command_result, _, new_lines = result
7402
7534
  assert_equal @mock_result, command_result
7403
- assert_equal 0, new_lines.length # No exportable variables
7535
+ assert_equal 0, command_result.new_lines.length # No exportable variables
7404
7536
  end
7405
7537
 
7406
7538
  # Test EXIT_STATUS_REQUIRED_EMPTY handling
7407
7539
  def test_exit_status_required_empty_with_warning
7408
- failed_result = OpenStruct.new(
7409
- exit_status: 248, # EXIT_STATUS_REQUIRED_EMPTY
7540
+ failed_result = CommandResult.new(
7541
+ exit_status: CommandResult::EXIT_STATUS_REQUIRED_EMPTY,
7542
+ exportable: false,
7410
7543
  stdout: '',
7411
7544
  stderr: 'Required variable empty',
7412
7545
  warning: nil
7413
7546
  )
7414
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([failed_result,
7415
- false])
7547
+ HashDelegator.stubs(:execute_bash_script_lines).returns(failed_result)
7416
7548
  @hd.stubs(:warning_required_empty).returns('Warning: required empty')
7417
7549
 
7418
- result = @hd.ux_block_eval_for_export(
7550
+ command_result = @hd.ux_block_eval_for_export(
7419
7551
  @bash_script_lines, @export,
7420
7552
  data: 'echo ""',
7421
7553
  force: false,
7422
7554
  silent: false
7423
7555
  )
7424
7556
 
7425
- command_result, exportable, new_lines = result
7426
7557
  assert_equal failed_result, command_result
7427
- assert_equal false, exportable
7428
- assert_equal 0, new_lines.length
7429
7558
  assert_equal 'Warning: required empty', command_result.warning
7430
7559
  end
7431
7560
 
7432
7561
  # Test EXIT_STATUS_REQUIRED_EMPTY handling with silent flag
7433
7562
  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(
7563
+ command_result = @hd.ux_block_eval_for_export(
7444
7564
  @bash_script_lines, @export,
7445
7565
  data: 'echo ""',
7446
7566
  force: false,
7447
7567
  silent: true
7448
7568
  )
7449
7569
 
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
7570
+ assert_empty command_result.warning # No warning when silent
7454
7571
  end
7455
7572
 
7456
7573
  # Test string parameter override
7457
7574
  def test_string_parameter_override
7458
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7459
- true])
7575
+ @mock_result.exportable = true
7576
+ HashDelegator.stubs(:execute_bash_script_lines).returns(
7577
+ @mock_result
7578
+ )
7460
7579
  @hd.stubs(:variable_is_exportable).returns(true)
7461
7580
 
7462
- result = @hd.ux_block_eval_for_export(
7581
+ command_result = @hd.ux_block_eval_for_export(
7463
7582
  @bash_script_lines, @export,
7464
7583
  data: 'original_data',
7465
7584
  string: 'override_string',
@@ -7468,136 +7587,115 @@ module MarkdownExec
7468
7587
  )
7469
7588
 
7470
7589
  # The function should use string parameter instead of data
7471
- command_result, exportable, = result
7472
7590
  assert_equal @mock_result, command_result
7473
- assert_equal true, exportable
7474
7591
  end
7475
7592
 
7476
7593
  # Edge case: empty string
7477
7594
  def test_empty_string_input
7478
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7479
- true])
7595
+ @mock_result.exportable = true
7596
+ HashDelegator.stubs(:execute_bash_script_lines).returns(
7597
+ @mock_result
7598
+ )
7480
7599
  @hd.stubs(:variable_is_exportable).returns(true)
7481
7600
 
7482
- result = @hd.ux_block_eval_for_export(
7601
+ command_result = @hd.ux_block_eval_for_export(
7483
7602
  @bash_script_lines, @export,
7484
7603
  data: '',
7485
7604
  force: false,
7486
7605
  silent: false
7487
7606
  )
7488
7607
 
7489
- command_result, exportable, = result
7490
7608
  assert_equal @mock_result, command_result
7491
- assert_equal true, exportable
7492
7609
  end
7493
7610
 
7494
7611
  # Edge case: nil data (uses string.nil? check)
7495
7612
  def test_nil_data_input
7496
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7497
- true])
7613
+ @mock_result.exportable = true
7614
+ HashDelegator.stubs(:execute_bash_script_lines).returns(
7615
+ @mock_result
7616
+ )
7498
7617
  @hd.stubs(:variable_is_exportable).returns(true)
7499
7618
 
7500
- result = @hd.ux_block_eval_for_export(
7619
+ command_result = @hd.ux_block_eval_for_export(
7501
7620
  @bash_script_lines, @export,
7502
7621
  data: 'd1',
7503
7622
  force: false,
7504
7623
  silent: false
7505
7624
  )
7506
7625
 
7507
- command_result, exportable, = result
7508
7626
  assert_equal @mock_result, command_result
7509
- assert_equal true, exportable
7510
7627
  end
7511
7628
 
7512
7629
  # Edge case: empty hash
7513
7630
  def test_empty_hash_input
7514
- result = @hd.ux_block_eval_for_export(
7631
+ command_result = @hd.ux_block_eval_for_export(
7515
7632
  @bash_script_lines, @export,
7516
7633
  data: {},
7517
7634
  force: false,
7518
7635
  silent: false
7519
7636
  )
7520
7637
 
7521
- command_result, exportable, new_lines = result
7522
7638
  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
7639
  end
7526
7640
 
7527
7641
  # Edge case: hash with nil values
7528
7642
  def test_hash_with_nil_values
7529
7643
  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(
7644
+ command_result = @hd.ux_block_eval_for_export(
7536
7645
  @bash_script_lines, @export,
7537
7646
  data: hash_data,
7538
7647
  force: false,
7648
+ printf_expand: true,
7539
7649
  silent: false
7540
7650
  )
7541
7651
 
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
7652
+ assert_equal 2, command_result.new_lines.length
7546
7653
  end
7547
7654
 
7548
7655
  # Edge case: unsupported input type (Array)
7549
7656
  def test_unsupported_input_type_array
7550
- result = @hd.ux_block_eval_for_export(
7657
+ command_result = @hd.ux_block_eval_for_export(
7551
7658
  @bash_script_lines, @export,
7552
7659
  data: %w[item1 item2],
7553
7660
  force: false,
7554
7661
  silent: false
7555
7662
  )
7556
7663
 
7557
- command_result, exportable, new_lines = result
7558
7664
  assert_nil command_result # No processing for unsupported types
7559
- assert_equal true, exportable # Initial value
7560
- assert_equal 0, new_lines.length
7561
7665
  end
7562
7666
 
7563
7667
  # Edge case: unsupported input type (custom object)
7564
7668
  def test_unsupported_input_type_object
7565
7669
  custom_object = Object.new
7566
- result = @hd.ux_block_eval_for_export(
7670
+ command_result = @hd.ux_block_eval_for_export(
7567
7671
  @bash_script_lines, @export,
7568
7672
  data: custom_object,
7569
7673
  force: false,
7570
7674
  silent: false
7571
7675
  )
7572
7676
 
7573
- command_result, exportable, new_lines = result
7574
7677
  assert_nil command_result # No processing for unsupported types
7575
- assert_equal true, exportable # Initial value
7576
- assert_equal 0, new_lines.length
7577
7678
  end
7578
7679
 
7579
7680
  # Test error handling - StandardError rescue
7580
7681
  def test_standard_error_rescue
7581
7682
  # Should not raise, but return nil due to rescue block
7582
- result = @hd.ux_block_eval_for_export(
7683
+ command_result = @hd.ux_block_eval_for_export(
7583
7684
  @bash_script_lines, @export,
7584
7685
  data: 'test_data',
7585
7686
  force: false,
7586
7687
  silent: false
7587
7688
  )
7588
7689
 
7589
- command_result, = result
7590
- assert_equal 127, command_result.exit_status
7690
+ assert_equal CommandResult::EXIT_STATUS_FAIL, command_result.exit_status
7591
7691
  end
7592
7692
 
7593
7693
  # Test environment variable setting
7594
7694
  def test_environment_variable_setting
7595
- @hd.stubs(:output_from_adhoc_bash_script_file).returns([@mock_result,
7596
- true])
7597
7695
  @hd.stubs(:variable_is_exportable).returns(true)
7598
7696
 
7599
7697
  # Mock EnvInterface.set to verify it's called
7600
- EnvInterface.expects(:set).with('TEST_VAR', 'test_output')
7698
+ EnvInterface.expects(:set).with('TEST_VAR', "test\n")
7601
7699
 
7602
7700
  @hd.ux_block_eval_for_export(
7603
7701
  @bash_script_lines, @export,
@@ -7609,10 +7707,6 @@ module MarkdownExec
7609
7707
 
7610
7708
  # Test join_array_of_arrays is called correctly
7611
7709
  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
7710
  # Mock join_array_of_arrays to verify it's called with correct parameters
7617
7711
  @hd.expects(:join_array_of_arrays).with(
7618
7712
  @bash_script_lines,