markdown_exec 2.8.1 → 2.8.3

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.
@@ -306,7 +306,7 @@ module HashDelegatorSelf
306
306
  table__hs.each do |table_hs|
307
307
  table_hs.substrings.each do |substrings|
308
308
  substrings.each do |node|
309
- next unless node[:text].class == TrackedString
309
+ next unless node[:text].instance_of?(TrackedString)
310
310
 
311
311
  exceeded_table_cell ||= node[:text].exceeded
312
312
  truncated_table_cell = node[:text].truncated
@@ -395,10 +395,17 @@ module HashDelegatorSelf
395
395
  # @param [String] line The line to be processed.
396
396
  # @param [Array<Symbol>] selected_types A list of message types to check.
397
397
  # @param [Proc] block The block to be called with the line data.
398
- def yield_line_if_selected(line, selected_types, source_id: '', &block)
398
+ def yield_line_if_selected(line, selected_types, all_fcbs: nil,
399
+ source_id: '', &block)
399
400
  return unless block && block_type_selected?(selected_types, :line)
400
401
 
401
- block.call(:line, MarkdownExec::FCB.new(body: [line], id: source_id))
402
+ block.call(:line, persist_fcb_self(all_fcbs, body: [line], id: source_id))
403
+ end
404
+
405
+ def persist_fcb_self(all_fcbs, options)
406
+ fcb = MarkdownExec::FCB.new(options)
407
+ all_fcbs << fcb if all_fcbs
408
+ fcb
402
409
  end
403
410
  end
404
411
 
@@ -606,6 +613,10 @@ module MarkdownExec
606
613
 
607
614
  @process_mutex = Mutex.new
608
615
  @process_cv = ConditionVariable.new
616
+ @dml_link_state = Struct.new(:document_filename, :inherited_lines)
617
+ .new(@delegate_object[:filename], [])
618
+ @dml_menu_blocks = []
619
+ @fcb_store = [] # all fcbs created
609
620
 
610
621
  @p_all_arguments = []
611
622
  @p_options_parsed = []
@@ -734,7 +745,7 @@ module MarkdownExec
734
745
 
735
746
  formatted_name = format(@delegate_object[:menu_link_format],
736
747
  HashDelegator.safeval(option_name))
737
- chrome_block = FCB.new(
748
+ chrome_block = persist_fcb(
738
749
  chrome: true,
739
750
  dname: HashDelegator.new(@delegate_object).string_send_color(
740
751
  formatted_name, :menu_chrome_color
@@ -778,7 +789,7 @@ module MarkdownExec
778
789
  chrome_blocks = link_state.inherited_lines_map do |line|
779
790
  formatted = format(@delegate_object[:menu_inherited_lines_format],
780
791
  { line: line })
781
- FCB.new(
792
+ persist_fcb(
782
793
  chrome: true,
783
794
  disabled: TtyMenu::DISABLE,
784
795
  dname: HashDelegator.new(@delegate_object).string_send_color(
@@ -837,25 +848,6 @@ module MarkdownExec
837
848
  end
838
849
  end
839
850
 
840
- # private
841
-
842
- def expand_references!(fcb, link_state)
843
- expand_variable_references!(
844
- blocks: [fcb],
845
- initial_code_required: false,
846
- link_state: link_state
847
- )
848
- expand_variable_references!(
849
- blocks: [fcb],
850
- echo_format: '%s',
851
- group_name: :command,
852
- initial_code_required: false,
853
- key_format: '$(%s)',
854
- link_state: link_state,
855
- pattern: options_command_substitution_regexp
856
- )
857
- end
858
-
859
851
  # Iterates through nested files to collect various types
860
852
  # of blocks, including dividers, tasks, and others.
861
853
  # The method categorizes blocks based on their type and processes them accordingly.
@@ -910,30 +902,6 @@ module MarkdownExec
910
902
  HashDelegator.error_handler('blocks_from_nested_files')
911
903
  end
912
904
 
913
- # find a block by its original (undecorated) name or nickname (not visible in menu)
914
- # if matched, the block returned has properties that it is from cli and not ui
915
- def block_state_for_name_from_cli(block_name)
916
- SelectedBlockMenuState.new(
917
- blocks_find_by_block_name(@dml_blocks_in_file, block_name),
918
- OpenStruct.new(
919
- block_name_from_cli: true,
920
- block_name_from_ui: false
921
- ),
922
- MenuState::CONTINUE
923
- )
924
- end
925
-
926
- def blocks_find_by_block_name(blocks, block_name)
927
- @dml_blocks_in_file.find do |item|
928
- # 2024-08-04 match oname for long block names
929
- # 2024-08-04 match nickname for long block names
930
- block_name == item.pub_name || block_name == item.nickname || block_name == item.oname
931
- end || @dml_menu_blocks.find do |item|
932
- # 2024-08-22 search in menu blocks to allow matching of automatic chrome with nickname
933
- block_name == item.pub_name || block_name == item.nickname || block_name == item.oname
934
- end
935
- end
936
-
937
905
  def build_menu_options(exit_option, display_mode_option,
938
906
  menu_entries, display_format)
939
907
  [exit_option,
@@ -1022,25 +990,11 @@ module MarkdownExec
1022
990
  ]
1023
991
  end
1024
992
 
1025
- def neval(export_exec, inherited_code)
1026
- # ww0 export_exec
1027
- # ww0 inherited_code
1028
- code = (inherited_code || []) + [export_exec]
1029
- filespec = generate_temp_filename
1030
- File.write filespec, HashDelegator.join_code_lines(code)
1031
- File.chmod 0o755, filespec
1032
- # ww0 File.read(filespec)
1033
- ret = `#{filespec}`
1034
- File.delete filespec
1035
- ret
1036
- end
1037
-
1038
- # sets ENV
993
+ # parse YAML body defining the UX for a single variable
994
+ # set ENV value for the variable and return code lines for the same
1039
995
  def code_from_ux_block_to_set_environment_variables(
1040
996
  selected, mdoc, inherited_code: nil, force: true, only_default: false
1041
997
  )
1042
- # ww0 inherited_code
1043
- # ww0 mdoc
1044
998
  exit_prompt = @delegate_object[:prompt_filespec_back]
1045
999
 
1046
1000
  required = mdoc.collect_recursively_required_code(
@@ -1050,105 +1004,129 @@ module MarkdownExec
1050
1004
  block_source: block_source
1051
1005
  )
1052
1006
 
1007
+ # process each ux block in sequence, setting ENV and collecting lines
1053
1008
  code_lines = []
1054
- case data = YAML.load(selected.body.join("\n"))
1055
- when Hash
1056
- export = parse_yaml_of_ux_block(
1057
- data,
1058
- prompt: @delegate_object[:prompt_ux_enter_a_value],
1059
- validate: '^(?<name>[^ ].*)$'
1060
- )
1009
+ required[:blocks].each do |block|
1010
+ next unless block.type == BlockType::UX
1011
+
1012
+ case data = YAML.load(block.body.join("\n"))
1013
+ when Hash
1014
+ export = parse_yaml_of_ux_block(
1015
+ data,
1016
+ prompt: @delegate_object[:prompt_ux_enter_a_value],
1017
+ validate: '^(?<name>[^ ].*)$'
1018
+ )
1019
+
1020
+ # preconditions are variable names that must be set before the UX block is executed.
1021
+ # if any precondition is not set, the sequence is aborted.
1022
+ export.preconditions&.each do |precondition|
1023
+ code_lines.push "[[ -z $#{precondition} ]] && exit 1"
1024
+ end
1061
1025
 
1062
- exportable = true
1063
- if only_default
1064
- value = case export.default
1065
- # exec > default
1066
- when :exec
1067
- raise unless export.exec.present?
1026
+ exportable = true
1027
+ if only_default
1028
+ value = case export.default
1029
+ # echo > default
1030
+ when :echo
1031
+ raise unless export.echo.present?
1032
+
1033
+ output = export_echo_with_code(
1034
+ export, inherited_code, code_lines, required
1035
+ )
1036
+ if output == :invalidated
1037
+ return :ux_exec_prohibited
1038
+ end
1068
1039
 
1069
- n1 = neval(export.exec,
1070
- (inherited_code || []) + required[:code])
1040
+ transform_export_value(output, export)
1071
1041
 
1072
- if export.transform.present?
1073
- if export.transform.is_a? Symbol
1074
- n1.send(export.transform)
1075
- else
1076
- format(
1077
- export.transform,
1078
- NamedCaptureExtractor.extract_named_groups(
1079
- n1, export.validate
1080
- )
1081
- )
1042
+ # exec > default
1043
+ when :exec
1044
+ raise unless export.exec.present?
1045
+
1046
+ output = export_exec_with_code(
1047
+ export, inherited_code, code_lines, required
1048
+ )
1049
+ if output == :invalidated
1050
+ return :ux_exec_prohibited
1082
1051
  end
1052
+
1053
+ transform_export_value(output, export)
1054
+
1055
+ # default
1083
1056
  else
1084
- n1
1057
+ export.default.to_s
1085
1058
  end
1059
+ else
1060
+ value = nil
1086
1061
 
1087
- # default
1088
- else
1089
- export.default.to_s
1090
- end
1091
- else
1092
- caps = nil
1093
- value = nil
1094
-
1095
- # exec > allowed
1096
- if export.exec
1097
- value = neval(export.exec, (inherited_code || []) + required[:code])
1098
- caps = NamedCaptureExtractor.extract_named_groups(value,
1099
- export.validate)
1100
-
1101
- # allowed > prompt
1102
- elsif export.allowed && export.allowed.count.positive?
1103
- case (choice = prompt_select_code_filename(
1104
- [exit_prompt] + export.allowed,
1105
- string: export.prompt,
1106
- color_sym: :prompt_color_after_script_execution
1107
- ))
1108
- when exit_prompt
1109
- exportable = false
1110
- else
1111
- value = choice
1112
- caps = NamedCaptureExtractor.extract_named_groups(value,
1113
- export.validate)
1114
- end
1062
+ # echo > exec
1063
+ if export.echo
1064
+ value = export_echo_with_code(
1065
+ export, inherited_code, code_lines, required
1066
+ )
1067
+ if value == :invalidated
1068
+ return :ux_exec_prohibited
1069
+ end
1115
1070
 
1116
- # prompt > default
1117
- elsif export.prompt.present?
1118
- begin
1119
- while true
1120
- print "#{export.prompt} [#{export.default}]: "
1121
- value = gets.chomp
1122
- value = export.default.to_s if value.empty?
1123
- caps = NamedCaptureExtractor.extract_named_groups(value,
1124
- export.validate)
1125
- break if caps
1071
+ # exec > allowed
1072
+ elsif export.exec
1073
+ value = export_exec_with_code(
1074
+ export, inherited_code, code_lines, required
1075
+ )
1076
+ if value == :invalidated
1077
+ return :ux_exec_prohibited
1078
+ end
1126
1079
 
1127
- # invalid input, retry
1080
+ # allowed > prompt
1081
+ elsif export.allowed && export.allowed.count.positive?
1082
+ case (choice = prompt_select_code_filename(
1083
+ [exit_prompt] + export.allowed,
1084
+ string: export.prompt,
1085
+ color_sym: :prompt_color_after_script_execution
1086
+ ))
1087
+ when exit_prompt
1088
+ exportable = false
1089
+ else
1090
+ value = choice
1091
+ end
1128
1092
 
1093
+ # prompt > default
1094
+ elsif export.prompt.present?
1095
+ begin
1096
+ loop do
1097
+ print "#{export.prompt} [#{export.default}]: "
1098
+ value = gets.chomp
1099
+ value = export.default.to_s if value.empty?
1100
+ caps = NamedCaptureExtractor.extract_named_groups(value,
1101
+ export.validate)
1102
+ break if caps
1103
+
1104
+ # invalid input, retry
1105
+ end
1106
+ rescue Interrupt
1107
+ exportable = false
1129
1108
  end
1130
- rescue Interrupt
1131
- exportable = false
1109
+
1110
+ # default
1111
+ else
1112
+ value = export.default
1132
1113
  end
1133
1114
 
1134
- # default
1135
- else
1136
- value = export.default
1115
+ if exportable
1116
+ value = transform_export_value(value, export)
1117
+ end
1137
1118
  end
1138
1119
 
1139
- if exportable && export.transform.present?
1140
- value = format(export.transform, caps)
1120
+ if exportable
1121
+ ENV[export.name] = value.to_s
1122
+ code_lines.push code_line_safe_assign(export.name, value,
1123
+ force: force)
1141
1124
  end
1125
+ else
1126
+ raise "Invalid data type: #{data.inspect}"
1142
1127
  end
1143
-
1144
- if exportable
1145
- ENV[export.name] = value.to_s
1146
- code_lines.push code_line_safe_assign(export.name, value,
1147
- force: force)
1148
- end
1149
- else
1150
- raise "Invalid data type: #{data.inspect}"
1151
1128
  end
1129
+
1152
1130
  code_lines
1153
1131
  end
1154
1132
 
@@ -1221,7 +1199,8 @@ module MarkdownExec
1221
1199
  command_execute_in_process(
1222
1200
  args: args, command: command,
1223
1201
  erls: erls,
1224
- filename: @delegate_object[:filename], shell: shell
1202
+ filename: @delegate_object[:filename],
1203
+ shell: shell
1225
1204
  )
1226
1205
  end
1227
1206
 
@@ -1285,7 +1264,8 @@ module MarkdownExec
1285
1264
  )
1286
1265
  execute_command_with_streams(
1287
1266
  [shell, '-c', command,
1288
- @delegate_object[:filename], *args]
1267
+ @delegate_object[:filename],
1268
+ *args]
1289
1269
  )
1290
1270
  end
1291
1271
 
@@ -1390,7 +1370,7 @@ module MarkdownExec
1390
1370
  next if exclude_types.include?(block.type)
1391
1371
 
1392
1372
  # Scan each block name for matches of the pattern
1393
- ([block.oname || ''] + block.body).join("\n").scan(pattern) do |(_, _variable_name)|
1373
+ count_named_group_occurrences_block_body_fix_indent(block).scan(pattern) do |(_, _variable_name)|
1394
1374
  pattern.match($LAST_MATCH_INFO.to_s) # Reapply match for named groups
1395
1375
  occurrence_count[$LAST_MATCH_INFO[group_name]] += 1
1396
1376
  end
@@ -1399,6 +1379,11 @@ module MarkdownExec
1399
1379
  occurrence_count
1400
1380
  end
1401
1381
 
1382
+ def count_named_group_occurrences_block_body_fix_indent(block)
1383
+ ### actually double the entries, but not a problem since it's used as a boolean
1384
+ ([block.oname || ''] + block.body).join("\n")
1385
+ end
1386
+
1402
1387
  ##
1403
1388
  # Creates and adds a formatted block to the blocks array
1404
1389
  # based on the provided match and format options.
@@ -1500,7 +1485,7 @@ module MarkdownExec
1500
1485
  fcb.type = type
1501
1486
  use_fcb = false # next line is new record
1502
1487
  else
1503
- fcb = FCB.new(
1488
+ fcb = persist_fcb(
1504
1489
  center: center,
1505
1490
  chrome: true,
1506
1491
  collapse: collapse.nil? ? (line_obj[:collapse] == COLLAPSIBLE_TOKEN_COLLAPSE) : collapse,
@@ -1587,7 +1572,7 @@ module MarkdownExec
1587
1572
  oname = format(@delegate_object[:menu_divider_format],
1588
1573
  HashDelegator.safeval(@delegate_object[divider_key]))
1589
1574
 
1590
- FCB.new(
1575
+ persist_fcb(
1591
1576
  chrome: true,
1592
1577
  disabled: TtyMenu::DISABLE,
1593
1578
  dname: string_send_color(oname, :menu_divider_color),
@@ -1987,9 +1972,7 @@ module MarkdownExec
1987
1972
  )
1988
1973
 
1989
1974
  # dname is not fixed for some block types, use block id
1990
- if lfls.load_file != LoadFile::LOAD &&
1991
- [BlockType::HEADING, BlockType::TEXT,
1992
- BlockType::UX].include?(selected.type)
1975
+ if lfls.load_file != LoadFile::LOAD
1993
1976
  block_selection = BlockSelection.new(selected.id)
1994
1977
  end
1995
1978
 
@@ -1999,7 +1982,7 @@ module MarkdownExec
1999
1982
  end
2000
1983
 
2001
1984
  def execute_block_in_state(block_name)
2002
- @dml_block_state = block_state_for_name_from_cli(block_name)
1985
+ @dml_block_state = find_block_state_by_name(block_name)
2003
1986
  dump_and_warn_block_state(name: block_name,
2004
1987
  selected: @dml_block_state.block)
2005
1988
  next_block_state =
@@ -2173,24 +2156,24 @@ module MarkdownExec
2173
2156
  else
2174
2157
  warn 'No matching file found.'
2175
2158
  end
2176
- elsif selected_option = select_option_with_metadata(
2159
+ elsif (selected_option = select_option_with_metadata(
2177
2160
  prompt_title,
2178
2161
  [exit_prompt] + dirs.map do |file|
2179
- { name: format(
2180
- block_data['view'] || view,
2181
- NamedCaptureExtractor.extract_named_group_match_data(
2182
- file.match(
2183
- Regexp.new(block_data['filename_pattern'] ||
2184
- filename_pattern)
2185
- )
2186
- )
2187
- ),
2162
+ { name:
2163
+ format(
2164
+ block_data['view'] || view,
2165
+ NamedCaptureExtractor.extract_named_group_match_data(
2166
+ file.match(
2167
+ Regexp.new(block_data['filename_pattern'] || filename_pattern)
2168
+ )
2169
+ )
2170
+ ),
2188
2171
  oname: file }
2189
2172
  end,
2190
2173
  menu_options.merge(
2191
2174
  cycle: true
2192
2175
  )
2193
- )
2176
+ ))
2194
2177
  if selected_option.dname != exit_prompt
2195
2178
  File.readlines(selected_option.oname, chomp: true)
2196
2179
  end
@@ -2341,7 +2324,7 @@ module MarkdownExec
2341
2324
 
2342
2325
  interactive_menu_with_display_modes(
2343
2326
  files_table_rows,
2344
- display_formats: [:row, :file],
2327
+ display_formats: %i[row file],
2345
2328
  display_mode_option: @delegate_object[:prompt_filespec_facet],
2346
2329
  exit_option: exit_prompt,
2347
2330
  menu_title: @delegate_object[:prompt_select_history_file],
@@ -2364,9 +2347,9 @@ module MarkdownExec
2364
2347
  def execute_inherited_save(
2365
2348
  code_lines: @dml_link_state.inherited_lines
2366
2349
  )
2367
- return unless save_filespec = save_filespec_from_expression(
2368
- document_name_in_glob_as_file_name
2369
- )
2350
+ return unless (save_filespec = save_filespec_from_expression)
2351
+
2352
+ document_name_in_glob_as_file_name
2370
2353
 
2371
2354
  unless write_file_with_directory_creation(
2372
2355
  content: HashDelegator.join_code_lines(code_lines),
@@ -2422,6 +2405,27 @@ module MarkdownExec
2422
2405
  post_execution_process
2423
2406
  end
2424
2407
 
2408
+ def execute_temporary_script(script_code, additional_code = [])
2409
+ full_code = (additional_code || []) + [script_code]
2410
+
2411
+ Tempfile.create('script_exec') do |temp_file|
2412
+ temp_file.write(HashDelegator.join_code_lines(full_code))
2413
+ temp_file.flush
2414
+ File.chmod(0o755, temp_file.path)
2415
+
2416
+ output = `#{temp_file.path}`
2417
+
2418
+ if $?.exitstatus != 0
2419
+ return :invalidated
2420
+ end
2421
+
2422
+ output
2423
+ end
2424
+ rescue StandardError => err
2425
+ warn "Error executing script: #{err.message}"
2426
+ nil
2427
+ end
2428
+
2425
2429
  def expand_blocks_with_replacements(
2426
2430
  menu_blocks, replacements, exclude_types: [BlockType::SHELL]
2427
2431
  )
@@ -2438,6 +2442,23 @@ module MarkdownExec
2438
2442
  end
2439
2443
  end
2440
2444
 
2445
+ def expand_references!(fcb, link_state)
2446
+ expand_variable_references!(
2447
+ blocks: [fcb],
2448
+ initial_code_required: false,
2449
+ link_state: link_state
2450
+ )
2451
+ expand_variable_references!(
2452
+ blocks: [fcb],
2453
+ echo_format: '%s',
2454
+ group_name: :command,
2455
+ initial_code_required: false,
2456
+ key_format: '$(%s)',
2457
+ link_state: link_state,
2458
+ pattern: options_command_substitution_regexp
2459
+ )
2460
+ end
2461
+
2441
2462
  def expand_variable_references!(
2442
2463
  blocks:,
2443
2464
  echo_format: 'echo $%s',
@@ -2468,6 +2489,30 @@ module MarkdownExec
2468
2489
  expand_blocks_with_replacements(blocks, replacements)
2469
2490
  end
2470
2491
 
2492
+ def export_echo_with_code(export, inherited_code, code_lines, required)
2493
+ value = execute_temporary_script(
2494
+ %Q{eval printf '%s' "#{export.echo}"},
2495
+ (inherited_code || []) +
2496
+ code_lines + required[:code]
2497
+ )
2498
+ if value == :invalidated
2499
+ warn "A value must exist for: #{export.preconditions.join(', ')}"
2500
+ end
2501
+ value
2502
+ end
2503
+
2504
+ def export_exec_with_code(export, inherited_code, code_lines, required)
2505
+ value = execute_temporary_script(
2506
+ export.exec,
2507
+ (inherited_code || []) +
2508
+ code_lines + required[:code]
2509
+ )
2510
+ if value == :invalidated
2511
+ warn "A value must exist for: #{export.preconditions.join(', ')}"
2512
+ end
2513
+ value
2514
+ end
2515
+
2471
2516
  # Retrieves a specific data symbol from the delegate object,
2472
2517
  # converts it to a string, and applies a color style
2473
2518
  # based on the specified color symbol.
@@ -2501,6 +2546,31 @@ module MarkdownExec
2501
2546
  { size: file_size, lines: line_count }
2502
2547
  end
2503
2548
 
2549
+ # Search in @dml_blocks_in_file first,
2550
+ # fallback to @dml_menu_blocks if not found.
2551
+ def find_block_by_name(blocks, block_name)
2552
+ match_block = ->(item) do
2553
+ [item.pub_name, item.nickname,
2554
+ item.oname, item.s2title].include?(block_name)
2555
+ end
2556
+
2557
+ @dml_blocks_in_file.find(&match_block) ||
2558
+ @dml_menu_blocks.find(&match_block)
2559
+ end
2560
+
2561
+ # find a block by its original (undecorated) name or nickname (not visible in menu)
2562
+ # if matched, the block returned has properties that it is from cli and not ui
2563
+ def find_block_state_by_name(block_name)
2564
+ SelectedBlockMenuState.new(
2565
+ find_block_by_name(@dml_blocks_in_file, block_name),
2566
+ OpenStruct.new(
2567
+ block_name_from_cli: true,
2568
+ block_name_from_ui: false
2569
+ ),
2570
+ MenuState::CONTINUE
2571
+ )
2572
+ end
2573
+
2504
2574
  def format_and_execute_command(
2505
2575
  code_lines:,
2506
2576
  erls:,
@@ -2509,6 +2579,7 @@ module MarkdownExec
2509
2579
  formatted_command = code_lines.flatten.join("\n")
2510
2580
  @fout.fout fetch_color(data_sym: :script_execution_head,
2511
2581
  color_sym: :script_execution_frame_color)
2582
+
2512
2583
  command_execute(
2513
2584
  formatted_command,
2514
2585
  args: @pass_args,
@@ -2580,7 +2651,7 @@ module MarkdownExec
2580
2651
  # commands to echo variables
2581
2652
  #
2582
2653
  commands = {}
2583
- variable_counts.each do |variable, _count|
2654
+ variable_counts.each_key do |variable|
2584
2655
  command = format(echo_format, variable)
2585
2656
  commands[variable] = command
2586
2657
  end
@@ -2618,10 +2689,8 @@ module MarkdownExec
2618
2689
  @process_mutex.synchronize do
2619
2690
  Thread.new do
2620
2691
  stream.each_line do |line|
2621
- line.strip!
2622
2692
  if @run_state.files.streams
2623
- @run_state.files.append_stream_line(file_type,
2624
- line)
2693
+ @run_state.files.append_stream_line(file_type, line)
2625
2694
  end
2626
2695
 
2627
2696
  puts line if @delegate_object[:output_stdout]
@@ -2637,7 +2706,6 @@ module MarkdownExec
2637
2706
  end
2638
2707
 
2639
2708
  def history_files(
2640
- link_state,
2641
2709
  direction: :reverse,
2642
2710
  filename: nil,
2643
2711
  home: Dir.pwd,
@@ -2670,7 +2738,7 @@ module MarkdownExec
2670
2738
  Regexp.new(@delegate_object.fetch(
2671
2739
  :fenced_start_and_end_regex, '^(?<indent> *)`{3,}'
2672
2740
  )),
2673
- fcb: MarkdownExec::FCB.new(id: 'INIT'),
2741
+ fcb: persist_fcb(id: 'INIT'),
2674
2742
  in_fenced_block: false,
2675
2743
  headings: []
2676
2744
  }
@@ -2950,19 +3018,24 @@ module MarkdownExec
2950
3018
  @ux_most_recent_filename = @delegate_object[:filename]
2951
3019
 
2952
3020
  (blocks.each.with_object([]) do |block, merged_options|
2953
- merged_options.push(
2954
- code_from_ux_block_to_set_environment_variables(
2955
- block,
2956
- mdoc,
2957
- force: @delegate_object[:ux_auto_load_force_default],
2958
- only_default: true
2959
- )
3021
+ code = code_from_ux_block_to_set_environment_variables(
3022
+ block,
3023
+ mdoc,
3024
+ force: @delegate_object[:ux_auto_load_force_default],
3025
+ only_default: true
2960
3026
  )
3027
+ if code == :ux_exec_prohibited
3028
+ merged_options
3029
+ else
3030
+ merged_options.push(code)
3031
+ end
2961
3032
  end).to_a
2962
3033
  end
2963
3034
 
2964
- def load_auto_vars_block(all_blocks,
2965
- block_name: @delegate_object[:document_load_vars_block_name])
3035
+ def load_auto_vars_block(
3036
+ all_blocks,
3037
+ block_name: @delegate_object[:document_load_vars_block_name]
3038
+ )
2966
3039
  unless block_name.present? &&
2967
3040
  @vars_most_recent_filename != @delegate_object[:filename]
2968
3041
  return
@@ -3103,7 +3176,7 @@ module MarkdownExec
3103
3176
 
3104
3177
  # load document shell block
3105
3178
  #
3106
- if code_lines = load_document_shell_block(all_blocks, mdoc: mdoc)
3179
+ if (code_lines = load_document_shell_block(all_blocks, mdoc: mdoc))
3107
3180
  next_state_set_code(nil, link_state, code_lines)
3108
3181
  link_state.inherited_lines = code_lines
3109
3182
  reload_blocks = true
@@ -3111,7 +3184,7 @@ module MarkdownExec
3111
3184
 
3112
3185
  # load document ux block
3113
3186
  #
3114
- if code_lines = load_auto_ux_block(all_blocks, mdoc)
3187
+ if (code_lines = load_auto_ux_block(all_blocks, mdoc))
3115
3188
  new_code = HashDelegator.code_merge(link_state.inherited_lines,
3116
3189
  code_lines)
3117
3190
  next_state_set_code(nil, link_state, new_code)
@@ -3121,7 +3194,7 @@ module MarkdownExec
3121
3194
 
3122
3195
  # load document vars block
3123
3196
  #
3124
- if code_lines = load_auto_vars_block(all_blocks)
3197
+ if (code_lines = load_auto_vars_block(all_blocks))
3125
3198
  new_code = HashDelegator.code_merge(link_state.inherited_lines,
3126
3199
  code_lines)
3127
3200
  next_state_set_code(nil, link_state, new_code)
@@ -3153,7 +3226,7 @@ module MarkdownExec
3153
3226
  #
3154
3227
  menu_blocks.each do |fcb|
3155
3228
  fcb.body = fcb.raw_body || fcb.body || []
3156
- fcb.dname = fcb.raw_dname || fcb.dname
3229
+ fcb.name_in_menu!(fcb.raw_dname || fcb.dname)
3157
3230
  fcb.s0printable = fcb.raw_s0printable || fcb.s0printable
3158
3231
  fcb.s1decorated = fcb.raw_s1decorated || fcb.s1decorated
3159
3232
  expand_references!(fcb, link_state)
@@ -3185,7 +3258,7 @@ module MarkdownExec
3185
3258
  #
3186
3259
  return unless block.nil?
3187
3260
 
3188
- chrome_block = FCB.new(
3261
+ chrome_block = persist_fcb(
3189
3262
  chrome: true,
3190
3263
  disabled: TtyMenu::DISABLE,
3191
3264
  dname: HashDelegator.new(@delegate_object).string_send_color(
@@ -3267,7 +3340,10 @@ module MarkdownExec
3267
3340
  next_state_set_code(
3268
3341
  selected,
3269
3342
  link_state,
3270
- HashDelegator.code_merge(link_state&.inherited_lines, code_lines)
3343
+ HashDelegator.code_merge(
3344
+ link_state&.inherited_lines,
3345
+ code_lines.is_a?(Array) ? code_lines : [] # no code for :ux_exec_prohibited
3346
+ )
3271
3347
  )
3272
3348
  end
3273
3349
 
@@ -3341,6 +3417,12 @@ module MarkdownExec
3341
3417
  prompt_select_continue == MenuState::EXIT
3342
3418
  end
3343
3419
 
3420
+ def persist_fcb(options)
3421
+ MarkdownExec::FCB.new(options).tap do |fcb|
3422
+ @fcb_store << fcb
3423
+ end
3424
+ end
3425
+
3344
3426
  def pop_add_current_code_to_head_and_trigger_load(
3345
3427
  link_state, block_names, code_lines,
3346
3428
  dependencies, selected, next_block_name: nil
@@ -3409,8 +3491,8 @@ module MarkdownExec
3409
3491
  #
3410
3492
  # @param all_blocks [Array<Hash>] The list of blocks from the file.
3411
3493
  def select_blocks(menu_blocks)
3412
- menu_blocks.select do |fcb|
3413
- !Filter.prepared_not_in_menu?(
3494
+ menu_blocks.reject do |fcb|
3495
+ Filter.prepared_not_in_menu?(
3414
3496
  @delegate_object,
3415
3497
  fcb,
3416
3498
  %i[block_name_include_match block_name_wrapper_match]
@@ -3659,16 +3741,16 @@ module MarkdownExec
3659
3741
 
3660
3742
  case @delegate_object[:publish_document_file_mode]
3661
3743
  when 'append'
3662
- File.write(pipe_path, message + "\n", mode: 'a')
3744
+ File.write(pipe_path, "#{message}\n", mode: 'a')
3663
3745
  when 'fifo'
3664
3746
  unless @vux_pipe_open
3665
3747
  unless File.exist?(pipe_path)
3666
- FileUtils.mkfifo(pipe_path)
3748
+ File.mkfifo(pipe_path)
3667
3749
  @vux_pipe_created = pipe_path
3668
3750
  end
3669
3751
  @vux_pipe_open = File.open(pipe_path, 'w')
3670
3752
  end
3671
- @vux_pipe_open.puts(message + "\n")
3753
+ @vux_pipe_open.puts("#{message}\n")
3672
3754
  @vux_pipe_open.flush
3673
3755
  when 'write'
3674
3756
  File.write(pipe_path, message)
@@ -3695,7 +3777,6 @@ module MarkdownExec
3695
3777
  regexp: @delegate_object[:saved_asset_match]
3696
3778
  )
3697
3779
  history_files(
3698
- @dml_link_state,
3699
3780
  filename:
3700
3781
  if asset.present?
3701
3782
  saved_asset_filename(asset, @dml_link_state)
@@ -3710,7 +3791,8 @@ module MarkdownExec
3710
3791
  end
3711
3792
 
3712
3793
  saved_asset = saved_asset_for_history(
3713
- file: file, form: form,
3794
+ file: file,
3795
+ form: form,
3714
3796
  match_info: $LAST_MATCH_INFO
3715
3797
  )
3716
3798
  saved_asset == :break ? nil : saved_asset
@@ -3959,7 +4041,7 @@ module MarkdownExec
3959
4041
 
3960
4042
  def screen_width
3961
4043
  width = @delegate_object[:screen_width]
3962
- if width && width.positive?
4044
+ if width&.positive?
3963
4045
  width
3964
4046
  else
3965
4047
  @delegate_object[:console_width]
@@ -3976,7 +4058,7 @@ module MarkdownExec
3976
4058
  end
3977
4059
 
3978
4060
  def select_document_if_multiple(options, files, prompt:)
3979
- return files if files.class == String
4061
+ return files if files.instance_of?(String)
3980
4062
  return files[0] if (count = files.count) == 1
3981
4063
 
3982
4064
  return unless count >= 2
@@ -3994,7 +4076,11 @@ module MarkdownExec
3994
4076
 
3995
4077
  # Presents a TTY prompt to select an option or exit,
3996
4078
  # returns metadata including option and selected
3997
- def select_option_with_metadata(prompt_text, menu_items, opts = {})
4079
+ def select_option_with_metadata(
4080
+ prompt_text, menu_items, opts = {}, menu_blocks: nil
4081
+ )
4082
+ @dml_menu_blocks = menu_blocks if menu_blocks
4083
+
3998
4084
  ## configure to environment
3999
4085
  #
4000
4086
  register_console_attributes(opts)
@@ -4039,23 +4125,16 @@ module MarkdownExec
4039
4125
  return
4040
4126
  end
4041
4127
 
4042
- selected = menu_items.find do |item|
4128
+ selected = @dml_menu_blocks.find do |item|
4043
4129
  if item.instance_of?(Hash)
4044
4130
  [item[:id], item[:name], item[:dname]].include?(selection)
4045
4131
  elsif item.instance_of?(MarkdownExec::FCB)
4046
- item.dname == selection || item.id == selection
4132
+ item.id == selection
4047
4133
  else
4048
4134
  item == selection
4049
4135
  end
4050
4136
  end
4051
4137
 
4052
- # new FCB if selected is not an object
4053
- if selected.instance_of?(String)
4054
- selected = FCB.new(dname: selected)
4055
- elsif selected.instance_of?(Hash)
4056
- selected = FCB.new(selected)
4057
- end
4058
-
4059
4138
  unless selected
4060
4139
  HashDelegator.error_handler('select_option_with_metadata',
4061
4140
  error: 'menu item not found')
@@ -4168,7 +4247,7 @@ module MarkdownExec
4168
4247
  TtyMenu::ENABLE
4169
4248
  end
4170
4249
 
4171
- MarkdownExec::FCB.new(
4250
+ persist_fcb(
4172
4251
  body: [],
4173
4252
  call: rest.match(
4174
4253
  Regexp.new(@delegate_object[:block_calls_scan])
@@ -4206,6 +4285,21 @@ module MarkdownExec
4206
4285
  HashDelegator.apply_color_from_hash(string, @delegate_object, color_sym)
4207
4286
  end
4208
4287
 
4288
+ def transform_export_value(value, export)
4289
+ return value unless export.transform.present?
4290
+
4291
+ if export.transform.is_a? Symbol
4292
+ value.send(export.transform)
4293
+ else
4294
+ format(
4295
+ export.transform,
4296
+ NamedCaptureExtractor.extract_named_groups(
4297
+ value, export.validate
4298
+ )
4299
+ )
4300
+ end
4301
+ end
4302
+
4209
4303
  ##
4210
4304
  # Processes an individual line within a loop, updating headings
4211
4305
  # and handling fenced code blocks.
@@ -4278,7 +4372,9 @@ module MarkdownExec
4278
4372
  # add line if it is depth 0 or option allows it
4279
4373
  #
4280
4374
  HashDelegator.yield_line_if_selected(
4281
- line, selected_types, source_id: source_id, &block
4375
+ line, selected_types,
4376
+ all_fcbs: @fcb_store,
4377
+ source_id: source_id, &block
4282
4378
  )
4283
4379
  end
4284
4380
  end
@@ -4317,7 +4413,7 @@ module MarkdownExec
4317
4413
  end
4318
4414
 
4319
4415
  def vux_execute_and_prompt(block_name)
4320
- @dml_block_state = block_state_for_name_from_cli(block_name)
4416
+ @dml_block_state = find_block_state_by_name(block_name)
4321
4417
  if @dml_block_state.block &&
4322
4418
  @dml_block_state.block.type == BlockType::OPTS
4323
4419
  debounce_reset
@@ -4372,7 +4468,7 @@ module MarkdownExec
4372
4468
 
4373
4469
  when formatted_choice_ostructs[:history].pub_name
4374
4470
  debounce_reset
4375
- return :break unless files_table_rows = vux_history_files_table_rows
4471
+ return :break unless (files_table_rows = vux_history_files_table_rows)
4376
4472
 
4377
4473
  execute_history_select(files_table_rows, stream: $stderr)
4378
4474
  return :break if pause_user_exit
@@ -4490,7 +4586,8 @@ module MarkdownExec
4490
4586
 
4491
4587
  inherited_block_names = []
4492
4588
  inherited_dependencies = {}
4493
- selected = FCB.new(oname: 'load_code')
4589
+ selected = persist_fcb(oname: 'load_code')
4590
+
4494
4591
  pop_add_current_code_to_head_and_trigger_load(
4495
4592
  @dml_link_state, inherited_block_names,
4496
4593
  code_lines, inherited_dependencies, selected
@@ -4498,9 +4595,9 @@ module MarkdownExec
4498
4595
  end
4499
4596
 
4500
4597
  def vux_load_inherited
4501
- return unless filespec = load_filespec_from_expression(
4598
+ return unless (filespec = load_filespec_from_expression(
4502
4599
  document_name_in_glob_as_file_name
4503
- )
4600
+ ))
4504
4601
 
4505
4602
  @dml_link_state.inherited_lines_append(
4506
4603
  File.readlines(filespec, chomp: true)
@@ -4619,7 +4716,6 @@ module MarkdownExec
4619
4716
  )
4620
4717
  if @delegate_object[:menu_for_history]
4621
4718
  history_files(
4622
- @dml_link_state,
4623
4719
  filename: saved_asset_filename(@delegate_object[:filename],
4624
4720
  @dml_link_state),
4625
4721
  path: @delegate_object[:saved_script_folder]
@@ -4759,7 +4855,7 @@ module MarkdownExec
4759
4855
  def vux_user_selected_block_name
4760
4856
  if @dml_link_state.block_name.present?
4761
4857
  # @prior_block_was_link = true
4762
- @dml_block_state.block = blocks_find_by_block_name(
4858
+ @dml_block_state.block = find_block_by_name(
4763
4859
  @dml_blocks_in_file,
4764
4860
  @dml_link_state.block_name
4765
4861
  )
@@ -4818,15 +4914,17 @@ module MarkdownExec
4818
4914
  menu_blocks.find do |block|
4819
4915
  block.dname.include?(prior_answer)
4820
4916
  end&.name
4821
- when Struct
4917
+ when Struct, MarkdownExec::FCB
4822
4918
  if prior_answer.id
4823
- menu_items.find_index do |block|
4919
+ # when switching documents, the prior answer will not be found
4920
+ (menu_blocks.find_index do |block|
4824
4921
  block[:id] == prior_answer.id
4825
- end + 1
4922
+ end || 0) + 1
4826
4923
  else
4827
4924
  prior_answer.index || prior_answer.name
4828
4925
  end
4829
4926
  end
4927
+
4830
4928
  # prior_answer value may not match if color is different from
4831
4929
  # originating menu (opts changed while processing)
4832
4930
  selection_opts = if selected_answer
@@ -5157,13 +5255,12 @@ module MarkdownExec
5157
5255
 
5158
5256
  class TestHashDelegatorAppendDivider < Minitest::Test
5159
5257
  def setup
5160
- @hd = HashDelegator.new
5161
- @hd.instance_variable_set(:@delegate_object, {
5162
- menu_divider_format: 'Format',
5163
- menu_initial_divider: 'Initial Divider',
5164
- menu_final_divider: 'Final Divider',
5165
- menu_divider_color: :color
5166
- })
5258
+ @hd = HashDelegator.new(
5259
+ menu_divider_color: :color,
5260
+ menu_divider_format: 'Format',
5261
+ menu_final_divider: 'Final Divider',
5262
+ menu_initial_divider: 'Initial Divider'
5263
+ )
5167
5264
  @hd.stubs(:string_send_color).returns('Formatted Divider')
5168
5265
  HashDelegator.stubs(:safeval).returns('Safe Value')
5169
5266
  end
@@ -5185,7 +5282,7 @@ module MarkdownExec
5185
5282
  end
5186
5283
 
5187
5284
  def test_append_divider_without_format
5188
- @hd.instance_variable_set(:@delegate_object, {})
5285
+ @hd = HashDelegator.new
5189
5286
  menu_blocks = []
5190
5287
  @hd.append_divider(menu_blocks: menu_blocks, position: :initial)
5191
5288
 
@@ -5223,7 +5320,6 @@ module MarkdownExec
5223
5320
  @hd = HashDelegator.new
5224
5321
  @hd.stubs(:iter_blocks_from_nested_files).yields(:blocks, FCB.new)
5225
5322
  @hd.stubs(:create_and_add_chrome_blocks)
5226
- @hd.instance_variable_set(:@delegate_object, {})
5227
5323
  HashDelegator.stubs(:error_handler)
5228
5324
  end
5229
5325
 
@@ -5234,7 +5330,7 @@ module MarkdownExec
5234
5330
  end
5235
5331
 
5236
5332
  def test_blocks_from_nested_files_with_no_chrome
5237
- @hd.instance_variable_set(:@delegate_object, { no_chrome: true })
5333
+ @hd = HashDelegator.new(no_chrome: true)
5238
5334
  @hd.expects(:create_and_add_chrome_blocks).never
5239
5335
 
5240
5336
  result = @hd.blocks_from_nested_files
@@ -5246,7 +5342,6 @@ module MarkdownExec
5246
5342
  class TestHashDelegatorCollectRequiredCodeLines < Minitest::Test
5247
5343
  def setup
5248
5344
  @hd = HashDelegator.new
5249
- @hd.instance_variable_set(:@delegate_object, {})
5250
5345
  @mdoc = mock('YourMDocClass')
5251
5346
  @selected = FCB.new(
5252
5347
  body: ['key: value'],
@@ -5272,15 +5367,13 @@ module MarkdownExec
5272
5367
  class TestHashDelegatorCommandOrUserSelectedBlock < Minitest::Test
5273
5368
  def setup
5274
5369
  @hd = HashDelegator.new
5275
- @hd.instance_variable_set(:@delegate_object, {})
5276
5370
  HashDelegator.stubs(:error_handler)
5277
5371
  @hd.stubs(:wait_for_user_selected_block)
5278
5372
  end
5279
5373
 
5280
5374
  def test_command_selected_block
5281
5375
  all_blocks = [{ oname: 'block1' }, { oname: 'block2' }]
5282
- @hd.instance_variable_set(:@delegate_object,
5283
- { block_name: 'block1' })
5376
+ @hd = HashDelegator.new(block_name: 'block1')
5284
5377
 
5285
5378
  result = @hd.load_cli_or_user_selected_block(all_blocks: all_blocks)
5286
5379
 
@@ -5309,10 +5402,10 @@ module MarkdownExec
5309
5402
 
5310
5403
  class TestHashDelegatorCountBlockInFilename < Minitest::Test
5311
5404
  def setup
5312
- @hd = HashDelegator.new
5313
- @hd.instance_variable_set(:@delegate_object,
5314
- { fenced_start_and_end_regex: '^```',
5315
- filename: '/path/to/file' })
5405
+ @hd = HashDelegator.new(
5406
+ fenced_start_and_end_regex: '^```',
5407
+ filename: '/path/to/file'
5408
+ )
5316
5409
  @hd.stubs(:cfile).returns(mock('cfile'))
5317
5410
  end
5318
5411
 
@@ -5421,7 +5514,6 @@ module MarkdownExec
5421
5514
  def setup
5422
5515
  @hd = HashDelegator.new
5423
5516
  @hd.instance_variable_set(:@fout, mock('fout'))
5424
- @hd.instance_variable_set(:@delegate_object, {})
5425
5517
  @hd.stubs(:string_send_color)
5426
5518
  end
5427
5519
 
@@ -5443,7 +5535,6 @@ module MarkdownExec
5443
5535
  class TestHashDelegatorFetchColor < Minitest::Test
5444
5536
  def setup
5445
5537
  @hd = HashDelegator.new
5446
- @hd.instance_variable_set(:@delegate_object, {})
5447
5538
  end
5448
5539
 
5449
5540
  def test_fetch_color_with_valid_data
@@ -5476,7 +5567,6 @@ module MarkdownExec
5476
5567
  class TestHashDelegatorFormatReferencesSendColor < Minitest::Test
5477
5568
  def setup
5478
5569
  @hd = HashDelegator.new
5479
- @hd.instance_variable_set(:@delegate_object, {})
5480
5570
  end
5481
5571
 
5482
5572
  def test_format_references_send_color_with_valid_data
@@ -5581,7 +5671,6 @@ module MarkdownExec
5581
5671
  # )
5582
5672
 
5583
5673
  # def history_files(
5584
- # link_state,
5585
5674
  # direction: :reverse,
5586
5675
  # filename: nil,
5587
5676
  # home: Dir.pwd,
@@ -5590,9 +5679,10 @@ module MarkdownExec
5590
5679
  # )
5591
5680
 
5592
5681
  def test_call
5593
- @hd.expects(:history_files).with(nil, filename: '*', path: nil).once
5594
- @hd.execute_block_type_history_ux(filename: '*', link_state: LinkState.new,
5595
- selected: FCB.new(body: []))
5682
+ @hd.expects(:history_files).with(filename: '*', path: nil).once
5683
+ @hd.execute_block_type_history_ux(
5684
+ filename: '*', link_state: LinkState.new, selected: FCB.new(body: [])
5685
+ )
5596
5686
  end
5597
5687
  end
5598
5688
 
@@ -5677,11 +5767,9 @@ module MarkdownExec
5677
5767
 
5678
5768
  class TestHashDelegatorHandleStream < Minitest::Test
5679
5769
  def setup
5680
- @hd = HashDelegator.new
5770
+ @hd = HashDelegator.new(output_stdout: true)
5681
5771
  @hd.instance_variable_set(:@run_state,
5682
5772
  OpenStruct.new(files: StreamsOut.new))
5683
- @hd.instance_variable_set(:@delegate_object,
5684
- { output_stdout: true })
5685
5773
  end
5686
5774
 
5687
5775
  def test_handle_stream
@@ -5691,7 +5779,7 @@ module MarkdownExec
5691
5779
  Thread.new { @hd.handle_stream(stream: stream, file_type: file_type) }
5692
5780
 
5693
5781
  @hd.wait_for_stream_processing
5694
- assert_equal ['line 1', 'line 2'],
5782
+ assert_equal ["line 1\n", "line 2\n"],
5695
5783
  @hd.instance_variable_get(:@run_state)
5696
5784
  .files.stream_lines(ExecutionStreams::STD_OUT)
5697
5785
  end
@@ -5713,9 +5801,7 @@ module MarkdownExec
5713
5801
 
5714
5802
  class TestHashDelegatorIterBlocksFromNestedFiles < Minitest::Test
5715
5803
  def setup
5716
- @hd = HashDelegator.new
5717
- @hd.instance_variable_set(:@delegate_object,
5718
- { filename: 'test.md' })
5804
+ @hd = HashDelegator.new(filename: 'test.md')
5719
5805
  @hd.stubs(:check_file_existence).with('test.md').returns(true)
5720
5806
  @hd.stubs(:initial_state).returns({})
5721
5807
  @hd.stubs(:cfile).returns(Minitest::Mock.new)
@@ -5744,12 +5830,11 @@ module MarkdownExec
5744
5830
 
5745
5831
  class TestHashDelegatorMenuChromeColoredOption < Minitest::Test
5746
5832
  def setup
5747
- @hd = HashDelegator.new
5748
- @hd.instance_variable_set(:@delegate_object, {
5749
- menu_option_back_name: 'Back',
5750
- menu_chrome_color: :red,
5751
- menu_chrome_format: '-- %s --'
5752
- })
5833
+ @hd = HashDelegator.new(
5834
+ menu_chrome_color: :red,
5835
+ menu_chrome_format: '-- %s --',
5836
+ menu_option_back_name: 'Back'
5837
+ )
5753
5838
  @hd.stubs(:menu_chrome_formatted_option)
5754
5839
  .with(:menu_option_back_name).returns('-- Back --')
5755
5840
  @hd.stubs(:string_send_color)
@@ -5763,8 +5848,9 @@ module MarkdownExec
5763
5848
  end
5764
5849
 
5765
5850
  def test_menu_chrome_colored_option_without_color
5766
- @hd.instance_variable_set(:@delegate_object,
5767
- { menu_option_back_name: 'Back' })
5851
+ @hd = HashDelegator.new(menu_option_back_name: 'Back')
5852
+ @hd.stubs(:menu_chrome_formatted_option)
5853
+ .with(:menu_option_back_name).returns('-- Back --')
5768
5854
  assert_equal '-- Back --',
5769
5855
  @hd.menu_chrome_colored_option(:menu_option_back_name)
5770
5856
  end
@@ -5772,11 +5858,10 @@ module MarkdownExec
5772
5858
 
5773
5859
  class TestHashDelegatorMenuChromeOption < Minitest::Test
5774
5860
  def setup
5775
- @hd = HashDelegator.new
5776
- @hd.instance_variable_set(:@delegate_object, {
5777
- menu_option_back_name: "'Back'",
5778
- menu_chrome_format: '-- %s --'
5779
- })
5861
+ @hd = HashDelegator.new(
5862
+ menu_chrome_format: '-- %s --',
5863
+ menu_option_back_name: "'Back'"
5864
+ )
5780
5865
  HashDelegator.stubs(:safeval).with("'Back'").returns('Back')
5781
5866
  end
5782
5867
 
@@ -5786,8 +5871,7 @@ module MarkdownExec
5786
5871
  end
5787
5872
 
5788
5873
  def test_menu_chrome_formatted_option_without_format
5789
- @hd.instance_variable_set(:@delegate_object,
5790
- { menu_option_back_name: "'Back'" })
5874
+ @hd = HashDelegator.new(menu_option_back_name: "'Back'")
5791
5875
  assert_equal 'Back',
5792
5876
  @hd.menu_chrome_formatted_option(:menu_option_back_name)
5793
5877
  end
@@ -5795,10 +5879,12 @@ module MarkdownExec
5795
5879
 
5796
5880
  class TestHashDelegatorStartFencedBlock < Minitest::Test
5797
5881
  def setup
5798
- @hd = HashDelegator.new({
5799
- block_name_wrapper_match: 'WRAPPER_REGEX',
5800
- block_calls_scan: 'CALLS_REGEX'
5801
- })
5882
+ @hd = HashDelegator.new(
5883
+ {
5884
+ block_calls_scan: 'CALLS_REGEX',
5885
+ block_name_wrapper_match: 'WRAPPER_REGEX'
5886
+ }
5887
+ )
5802
5888
  end
5803
5889
 
5804
5890
  def test_start_fenced_block
@@ -5816,9 +5902,7 @@ module MarkdownExec
5816
5902
 
5817
5903
  class TestHashDelegatorStringSendColor < Minitest::Test
5818
5904
  def setup
5819
- @hd = HashDelegator.new
5820
- @hd.instance_variable_set(:@delegate_object,
5821
- { red: 'red', green: 'green' })
5905
+ @hd = HashDelegator.new(red: 'red', green: 'green')
5822
5906
  end
5823
5907
 
5824
5908
  def test_string_send_color