markdown_exec 3.0.8 → 3.1.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -0
  3. data/Gemfile.lock +1 -1
  4. data/bats/block-type-ux-required-variables.bats +0 -12
  5. data/bats/block-type-ux-sources.bats +0 -8
  6. data/bats/cli.bats +12 -0
  7. data/bats/import-with-text-substitution.bats +9 -0
  8. data/bats/load-vars-state-demo.bats +26 -0
  9. data/bats/options.bats +1 -1
  10. data/docs/dev/import-substitution-basic.md +5 -0
  11. data/docs/dev/import-substitution-compare.md +12 -0
  12. data/docs/dev/import-substitution-export.md +18 -0
  13. data/docs/dev/import-substitution-long.md +12 -0
  14. data/docs/dev/import-substitution-mixed.md +9 -0
  15. data/docs/dev/import-substitution-plant.md +11 -0
  16. data/docs/dev/import-substitution-quotes.md +7 -0
  17. data/docs/dev/import-substitution-research.md +11 -0
  18. data/docs/dev/import-substitution-simple.md +5 -0
  19. data/docs/dev/import-substitution-special.md +10 -0
  20. data/docs/dev/import-substitution-taxonomy.md +14 -0
  21. data/docs/dev/import-with-text-substitution.md +57 -0
  22. data/docs/dev/linked-file.md +3 -0
  23. data/docs/dev/load-mode-demo.md +163 -0
  24. data/docs/dev/requiring-blocks.md +1 -0
  25. data/examples/import_with_substitution_demo.md +48 -0
  26. data/examples/imports/mixed_template.md +33 -0
  27. data/examples/imports/organism_template.md +42 -0
  28. data/examples/imports/template_mustache.md +22 -0
  29. data/examples/imports/template_vars.md +22 -0
  30. data/examples/raw_replacement_demo.md +42 -0
  31. data/examples/recent_discoveries_demo.md +43 -0
  32. data/examples/template_syntax_demo.md +24 -0
  33. data/lib/cached_nested_file_reader.rb +174 -28
  34. data/lib/command_result.rb +3 -2
  35. data/lib/constants.rb +5 -0
  36. data/lib/evaluate_shell_expressions.rb +0 -1
  37. data/lib/exceptions.rb +10 -2
  38. data/lib/fcb.rb +20 -14
  39. data/lib/hash_delegator.rb +192 -125
  40. data/lib/markdown_exec/version.rb +1 -1
  41. data/lib/mdoc.rb +2 -1
  42. data/lib/menu.src.yml +2 -1
  43. data/lib/menu.yml +2 -1
  44. data/lib/ww.rb +24 -0
  45. metadata +25 -2
@@ -954,7 +954,7 @@ module MarkdownExec
954
954
  blocks << fcb unless result.failure?
955
955
  end
956
956
  rescue StandardError
957
- # ww $@, $!
957
+ ww $@, $!, caller.deref
958
958
  HashDelegator.error_handler('blocks_from_nested_files',
959
959
  { abort: true })
960
960
  end
@@ -978,8 +978,8 @@ module MarkdownExec
978
978
  end
979
979
  OpenStruct.new(blocks: blocks, results: results)
980
980
  rescue StandardError
981
- # ww $@, $!
982
- HashDelegator.error_handler('blocks_from_nested_files')
981
+ wwe 'link_state:', link_state, 'source_id:', source_id, 'btype:', btype,
982
+ 'fcb:', fcb
983
983
  end
984
984
 
985
985
  def build_menu_options(exit_option, display_mode_option,
@@ -994,9 +994,6 @@ module MarkdownExec
994
994
  initial_code_required: false,
995
995
  occurrence_expressions: nil
996
996
  )
997
- ww commands
998
- ww link_state
999
- # binding.irb####
1000
997
  evaluate_shell_expressions(
1001
998
  (link_state&.inherited_lines_block || ''),
1002
999
  commands,
@@ -1113,24 +1110,26 @@ ww link_state
1113
1110
  selected, mdoc, inherited_code: nil, force: true, only_default: false,
1114
1111
  silent:
1115
1112
  )
1116
- ww selected
1113
+ wwt :fcb, 'ux block with code', selected
1117
1114
  ret_command_result = nil
1118
1115
  exit_prompt = @delegate_object[:prompt_filespec_back]
1119
1116
 
1120
- ww \
1121
1117
  required = mdoc.collect_recursively_required_code(
1122
1118
  anyname: selected.pub_name,
1123
1119
  label_format_above: @delegate_object[:shell_code_label_format_above],
1124
1120
  label_format_below: @delegate_object[:shell_code_label_format_below],
1125
1121
  block_source: block_source
1126
1122
  )
1123
+ wwt :required, required
1127
1124
 
1128
1125
  # process each ux block in sequence, setting ENV and collecting lines
1129
1126
  required_lines = []
1130
1127
  required[:blocks].each do |block|
1131
1128
  next unless block.type == BlockType::UX
1132
1129
 
1133
- case data = YAML.load(block.body.join("\n"))
1130
+ wwt :fcb, 'a required block', block
1131
+
1132
+ case data = safe_yaml_load(block.body.join("\n"))
1134
1133
  when Hash
1135
1134
  export = parse_yaml_of_ux_block(
1136
1135
  data,
@@ -1171,19 +1170,8 @@ ww \
1171
1170
  return command_result_w_e_t_nl if command_result_w_e_t_nl.failure?
1172
1171
 
1173
1172
  command_result_w_e_t_nl.new_lines =
1174
- command_result_w_e_t_nl.new_lines.map do |name_force|
1175
- transformed = if command_result_w_e_t_nl.transformable
1176
- transform_export_value(name_force[:text], export)
1177
- else
1178
- name_force[:text]
1179
-
1180
- end
1181
- EnvInterface.set(name_force[:name], transformed)
1182
-
1183
- code_line_safe_assign(
1184
- name_force[:name], transformed, force: name_force[:force]
1185
- )
1186
- end
1173
+ process_command_result_lines(command_result_w_e_t_nl, export,
1174
+ required_lines)
1187
1175
  required_lines.concat(command_result_w_e_t_nl.new_lines)
1188
1176
  ret_command_result = command_result_w_e_t_nl
1189
1177
  else
@@ -1191,24 +1179,23 @@ ww \
1191
1179
  end
1192
1180
  end
1193
1181
 
1194
- ret_command_result || CommandResult.new(stdout: required_lines)
1182
+ (ret_command_result || CommandResult.new(stdout: required_lines)).tap do |ret|
1183
+ wwt :cr, ret
1184
+ end
1195
1185
  rescue StandardError
1196
- ww $@, $!
1197
- HashDelegator.error_handler('code_from_ux_block_to_set_environment_variables')
1198
- end
1199
-
1200
- def env_set(name, value)
1201
- EnvInterface.set(name, value)
1186
+ wwe 'selected:', selected, 'required:', required, 'block:', block,
1187
+ 'data:', data
1202
1188
  end
1203
1189
 
1204
1190
  # sets ENV
1205
1191
  def code_from_vars_block_to_set_environment_variables(selected)
1206
1192
  code_lines = []
1193
+ # code_lines << '# code_from_vars_block_to_set_environment_variables'
1207
1194
  case data = YAML.load(selected.body.join("\n"))
1208
1195
  when Hash
1209
1196
  data.each do |key, value|
1210
1197
  EnvInterface.set(key, value.to_s)
1211
- code_lines.push "#{key}=#{Shellwords.escape(value)}"
1198
+ code_lines << assign_key_value_in_bash(key, value)
1212
1199
 
1213
1200
  next unless @delegate_object[:menu_vars_set_format].present?
1214
1201
 
@@ -1218,9 +1205,14 @@ ww \
1218
1205
  end
1219
1206
  end
1220
1207
  code_lines
1208
+ rescue StandardError
1209
+ wwe 'selected:', selected, 'data:', data, 'key:', key, 'value:', value,
1210
+ 'code_lines:', code_lines, 'formatted_string:', formatted_string
1221
1211
  end
1222
1212
 
1223
- def code_line_safe_assign(name, value, force:)
1213
+ # make a single line of shell code to assign an escaped value to a variable
1214
+ # force/default
1215
+ def code_line_to_assign_a_variable(name, value, force:)
1224
1216
  if force
1225
1217
  "#{name}=#{Shellwords.escape(value)}"
1226
1218
  else
@@ -1232,11 +1224,15 @@ ww \
1232
1224
  extract_patterns = lambda do |key|
1233
1225
  return [] unless delegate_object[key].present?
1234
1226
 
1235
- HashDelegator.safeval(delegate_object[key]).map do |pc|
1236
- {
1237
- color_method: pc[:color_method].to_sym,
1238
- pattern: Regexp.new(pc[:pattern])
1239
- }
1227
+ begin
1228
+ (value = HashDelegator.safeval(delegate_object[key])).map do |pc|
1229
+ {
1230
+ color_method: pc[:color_method].to_sym,
1231
+ pattern: Regexp.new(pc[:pattern])
1232
+ }
1233
+ end
1234
+ rescue StandardError
1235
+ wwe({ error: $!, callback: $@[0], key: key, value: value })
1240
1236
  end
1241
1237
  end
1242
1238
 
@@ -1706,10 +1702,6 @@ ww \
1706
1702
  @prior_execution_block = nil
1707
1703
  end
1708
1704
 
1709
- def selected_shell(shell_name)
1710
- shell_name.empty? ? shell : shell_name
1711
- end
1712
-
1713
1705
  # Determines the state of a selected block in the menu based
1714
1706
  # on the selected option.
1715
1707
  # It categorizes the selected option into either EXIT, BACK,
@@ -1932,6 +1924,10 @@ ww \
1932
1924
  result_text
1933
1925
  end
1934
1926
 
1927
+ def env_set(name, value)
1928
+ EnvInterface.set(name, value)
1929
+ end
1930
+
1935
1931
  # Execute a code block after approval and provide user interaction options.
1936
1932
  #
1937
1933
  # This method displays required code blocks, asks for user approval, and
@@ -1977,8 +1973,9 @@ ww \
1977
1973
 
1978
1974
  elsif selected.type == BlockType::LOAD
1979
1975
  debounce_reset
1980
- code_lines = execute_block_type_load_code_lines(selected)
1981
- next_state_append_code(selected, link_state, code_lines)
1976
+ load_result = execute_block_type_load_code_lines(selected)
1977
+ next_state_append_code(selected, link_state, load_result.code,
1978
+ mode: load_result.mode)
1982
1979
 
1983
1980
  elsif selected.type == BlockType::SAVE
1984
1981
  debounce_reset
@@ -2176,10 +2173,10 @@ ww \
2176
2173
  # load key and values from link block into current environment
2177
2174
  #
2178
2175
  if link_block_data[LinkKeys::VARS]
2179
- code_lines.push BashCommentFormatter.format_comment(selected.pub_name)
2180
- (link_block_data[LinkKeys::VARS] || []).each do |(key, value)|
2176
+ code_lines << BashCommentFormatter.format_comment(selected.pub_name)
2177
+ link_block_data[LinkKeys::VARS].each do |key, value|
2178
+ code_lines << assign_key_value_in_bash(key, value)
2181
2179
  EnvInterface.set(key, value.to_s)
2182
- code_lines.push(assign_key_value_in_bash(key, value))
2183
2180
  end
2184
2181
  end
2185
2182
 
@@ -2202,7 +2199,7 @@ ww \
2202
2199
  if link_block_data.fetch(LinkKeys::EVAL,
2203
2200
  false) || link_block_data.fetch(LinkKeys::EXEC,
2204
2201
  false)
2205
- code_lines = link_block_data_eval(
2202
+ code_lines += link_block_data_eval(
2206
2203
  link_state, code_lines, selected, link_block_data,
2207
2204
  block_source: block_source,
2208
2205
  shell: @delegate_object[:block_type_default]
@@ -2265,38 +2262,54 @@ ww \
2265
2262
  )
2266
2263
  dirs.sort_by! { |f| File.mtime(f) }.reverse!
2267
2264
 
2268
- if !contains_glob?(block_data['directory']) &&
2269
- !contains_glob?(block_data['glob'])
2270
- if dirs[0]
2271
- File.readlines(dirs[0], chomp: true)
2272
- else
2273
- warn 'No matching file found.'
2274
- end
2275
- elsif (selected_option = select_option_with_metadata(
2276
- prompt_title,
2277
- [exit_prompt] + dirs.map do |file| # tty_menu_items
2278
- { name:
2279
- format(
2280
- block_data['view'] || view,
2281
- NamedCaptureExtractor.extract_named_group_match_data(
2282
- file.match(
2283
- Regexp.new(block_data['filename_pattern'] || filename_pattern)
2284
- )
2285
- )
2286
- ),
2287
- oname: file }
2288
- end,
2289
- menu_options.merge(
2290
- cycle: true,
2291
- match_dml: false
2292
- )
2293
- ))
2294
- if selected_option.dname != exit_prompt
2295
- File.readlines(selected_option.oname, chomp: true)
2265
+ code = if !contains_glob?(block_data['directory']) &&
2266
+ !contains_glob?(block_data['glob'])
2267
+ if dirs[0]
2268
+ File.readlines(dirs[0], chomp: true)
2269
+ else
2270
+ warn 'No matching file found.'
2271
+ end
2272
+ elsif (selected_option = select_option_with_metadata(
2273
+ prompt_title,
2274
+ [exit_prompt] + dirs.map do |file| # tty_menu_items
2275
+ { name:
2276
+ format(
2277
+ block_data['view'] || view,
2278
+ NamedCaptureExtractor.extract_named_group_match_data(
2279
+ file.match(
2280
+ Regexp.new(block_data['filename_pattern'] || filename_pattern)
2281
+ )
2282
+ )
2283
+ ),
2284
+ oname: file }
2285
+ end,
2286
+ menu_options.merge(
2287
+ cycle: true,
2288
+ match_dml: false
2289
+ )
2290
+ ))
2291
+ if selected_option.dname != exit_prompt
2292
+ File.readlines(selected_option.oname, chomp: true)
2293
+ end
2294
+ else
2295
+ warn 'No matching files found.'
2296
+ end
2297
+
2298
+ if (mode = block_data['mode']&.to_sym)
2299
+ reason = 'specified Load mode'
2300
+ unless [LoadMode::APPEND, LoadMode::REPLACE].include? mode
2301
+ wwe 'Invalid mode', 'block_data:', block_data
2296
2302
  end
2297
2303
  else
2298
- warn 'No matching files found.'
2304
+ reason = 'default Load mode'
2305
+ mode = LoadMode::APPEND
2299
2306
  end
2307
+
2308
+ OpenStruct.new(
2309
+ code: code,
2310
+ mode: mode,
2311
+ reason: reason
2312
+ )
2300
2313
  end
2301
2314
 
2302
2315
  # Collects required code lines based on the selected block and
@@ -2540,7 +2553,7 @@ ww \
2540
2553
  end
2541
2554
 
2542
2555
  def expand_references!(fcb, link_state)
2543
- ww fcb, link_state, caller.deref
2556
+ # options expansions
2544
2557
  expand_variable_references!(
2545
2558
  blocks: [fcb],
2546
2559
  echo_formatter: method(:format_echo_command),
@@ -2583,22 +2596,15 @@ ww fcb, link_state, caller.deref
2583
2596
  link_state:,
2584
2597
  pattern:
2585
2598
  )
2586
- ww blocks####
2587
- ww link_state
2588
- # binding.irb
2589
2599
  variable_counts, occurrence_expressions = count_named_group_occurrences(
2590
2600
  blocks, pattern, group_name: group_name
2591
2601
  )
2592
2602
  return if variable_counts.nil? || variable_counts == {}
2593
- ww [variable_counts, occurrence_expressions]
2594
2603
 
2595
- ww \
2596
2604
  echo_commands = generate_echo_commands(
2597
2605
  variable_counts, formatter: echo_formatter
2598
2606
  )
2599
2607
 
2600
- ww link_state
2601
- ww \
2602
2608
  replacements = build_replacement_dictionary(
2603
2609
  echo_commands, link_state,
2604
2610
  initial_code_required: initial_code_required,
@@ -2608,11 +2614,9 @@ ww \
2608
2614
  return if replacements.nil?
2609
2615
  if replacements == EvaluateShellExpression::StatusFail
2610
2616
  # happens on first processing of blocks before requirements are met
2611
- ww "EvaluateShellExpression::StatusFail", echo_commands, link_state
2612
2617
  return
2613
2618
  end
2614
2619
 
2615
- ww \
2616
2620
  expand_blocks_with_replacements(blocks, replacements)
2617
2621
  # no return
2618
2622
  end
@@ -2620,17 +2624,20 @@ ww \
2620
2624
  def export_echo_with_code(
2621
2625
  bash_script_lines, export, force:, silent:, string: nil
2622
2626
  )
2627
+ wwp
2623
2628
  exportable = true
2624
2629
  command_result = nil
2625
2630
  new_lines = []
2626
2631
  export_string = string.nil? ? export.echo : string
2627
2632
  case export_string
2628
2633
  when String, Integer, Float, TrueClass, FalseClass
2629
- command_result, exportable, new_lines = output_from_adhoc_bash_script_file(
2634
+ command_result, exportable, = output_from_adhoc_bash_script_file(
2630
2635
  join_array_of_arrays(
2631
2636
  bash_script_lines,
2632
2637
  %(printf '%s' "#{export_string}")
2633
- )
2638
+ ),
2639
+ export,
2640
+ force: force
2634
2641
  )
2635
2642
  if command_result.exit_status == EXIT_STATUS_REQUIRED_EMPTY
2636
2643
  exportable = false
@@ -2648,7 +2655,9 @@ ww \
2648
2655
  join_array_of_arrays(
2649
2656
  bash_script_lines,
2650
2657
  %(printf '%s' "#{expression}")
2651
- )
2658
+ ),
2659
+ export,
2660
+ force: force
2652
2661
  )
2653
2662
  if command_result.exit_status == EXIT_STATUS_REQUIRED_EMPTY
2654
2663
  command_result.warning = warning_required_empty(export) unless silent
@@ -3015,7 +3024,11 @@ ww \
3015
3024
  # convert single items to arrays
3016
3025
  def join_array_of_arrays(*args)
3017
3026
  args.map do |item|
3018
- item.nil? ? nil : (item.is_a?(Array) ? item : [item])
3027
+ if item.nil?
3028
+ nil
3029
+ else
3030
+ (item.is_a?(Array) ? item : [item])
3031
+ end
3019
3032
  end.compact.flatten(1)
3020
3033
  end
3021
3034
 
@@ -3549,12 +3562,13 @@ ww \
3549
3562
  list[(index + 1) % list.size] # Get the next item, wrap around if at the end
3550
3563
  end
3551
3564
 
3552
- def next_state_append_code(selected, link_state, code_lines)
3565
+ def next_state_append_code(selected, link_state, code_lines,
3566
+ mode: LoadMode::APPEND)
3553
3567
  next_state_set_code(
3554
3568
  selected,
3555
3569
  link_state,
3556
3570
  HashDelegator.flatten_and_compact_arrays(
3557
- link_state&.inherited_lines,
3571
+ mode == LoadMode::APPEND ? link_state&.inherited_lines : [],
3558
3572
  code_lines.is_a?(Array) ? code_lines : [] # no code for :ux_exec_prohibited
3559
3573
  )
3560
3574
  )
@@ -3616,7 +3630,8 @@ ww \
3616
3630
 
3617
3631
  def output_from_adhoc_bash_script_file(
3618
3632
  bash_script_lines,
3619
- export = nil
3633
+ export = nil,
3634
+ force:
3620
3635
  )
3621
3636
  Tempfile.create('script_exec') do |temp_file|
3622
3637
  temp_file.write(
@@ -3637,25 +3652,22 @@ ww \
3637
3652
 
3638
3653
  output = `#{shell} #{temp_file.path}`
3639
3654
 
3640
- exportable = export ? export.exportable : false
3655
+ exportable = if export&.exportable.nil?
3656
+ true
3657
+ else
3658
+ (export ? export.exportable : false)
3659
+ end
3641
3660
  new_lines = []
3642
- if export
3643
- new_lines << "#{export.name}="
3644
- export_value = output
3645
- ### transform?
3661
+ # new_lines << { comment: 'output_from_adhoc_bash_script_file' }
3646
3662
 
3647
- command_result, exportable, new_lines = export_echo_with_code(
3648
- ["#{export.name}=#{Shellwords.escape(export_value)}"],
3649
- export,
3650
- force: force,
3651
- silent: silent,
3652
- string: export_value
3653
- )
3654
- else
3655
- command_result = CommandResult.new(
3656
- stdout: output,
3657
- exit_status: $?.exitstatus
3658
- )
3663
+ command_result = CommandResult.new(
3664
+ stdout: output,
3665
+ exit_status: $?.exitstatus
3666
+ )
3667
+ exportable &&= command_result.success?
3668
+ if exportable
3669
+ new_lines << { name: export.name, force: force,
3670
+ text: command_result.stdout }
3659
3671
  end
3660
3672
 
3661
3673
  [
@@ -3665,6 +3677,7 @@ ww \
3665
3677
  ]
3666
3678
  end
3667
3679
  rescue StandardError => err
3680
+ # wwe 'bash_script_lines:', bash_script_lines, 'export:', export
3668
3681
  warn "Error executing script: #{err.message}"
3669
3682
  nil
3670
3683
  end
@@ -3811,6 +3824,35 @@ ww \
3811
3824
 
3812
3825
  # private
3813
3826
 
3827
+ def process_command_result_lines(command_result_w_e_t_nl, export,
3828
+ required_lines)
3829
+ command_result_w_e_t_nl.new_lines.map do |name_force|
3830
+ comment = name_force.fetch(:comment, '')
3831
+ name = name_force.fetch(:name, '')
3832
+ if name.empty?
3833
+ "# #{comment}" unless comment.empty?
3834
+ else
3835
+ transformed = if command_result_w_e_t_nl.transformable
3836
+ transform_export_value(name_force[:text], export)
3837
+ else
3838
+ name_force[:text]
3839
+ end
3840
+
3841
+ # store the transformed value in ENV
3842
+ EnvInterface.set(name, transformed)
3843
+
3844
+ set = code_line_to_assign_a_variable(
3845
+ name, transformed, force: name_force[:force]
3846
+ )
3847
+
3848
+ comment.empty? ? set : "#{set} # #{comment}"
3849
+ end
3850
+ end
3851
+ rescue StandardError
3852
+ ww $@, $!, caller.deref
3853
+ raise StandardError, $!
3854
+ end
3855
+
3814
3856
  def process_string_array(arr, begin_pattern: nil, end_pattern: nil, scan1: nil,
3815
3857
  format1: nil, name: '')
3816
3858
  in_block = !begin_pattern.present?
@@ -4440,6 +4482,10 @@ ww \
4440
4482
  selected
4441
4483
  end
4442
4484
 
4485
+ def selected_shell(shell_name)
4486
+ shell_name.empty? ? shell : shell_name
4487
+ end
4488
+
4443
4489
  def shell
4444
4490
  @delegate_object[:shell]
4445
4491
  end
@@ -4575,7 +4621,6 @@ ww \
4575
4621
  return value unless export.transform.present?
4576
4622
 
4577
4623
  if export.transform.is_a? Symbol
4578
- ### TBD validate for symbol
4579
4624
  value.send(export.transform)
4580
4625
  else
4581
4626
  format(
@@ -4679,7 +4724,7 @@ ww \
4679
4724
  )
4680
4725
  command_result = nil
4681
4726
  exportable = true
4682
- force = true
4727
+ force = export.force.nil? ? true : export.force
4683
4728
  new_lines = []
4684
4729
  silent = false
4685
4730
  transformable = true
@@ -4713,7 +4758,9 @@ ww \
4713
4758
 
4714
4759
  when :exec, UxActSource::EXEC
4715
4760
  command_result, = output_from_adhoc_bash_script_file(
4716
- join_array_of_arrays(bash_script_lines, export.exec)
4761
+ join_array_of_arrays(bash_script_lines, export.exec),
4762
+ export,
4763
+ force: force
4717
4764
  )
4718
4765
 
4719
4766
  if command_result.exit_status == EXIT_STATUS_REQUIRED_EMPTY
@@ -4729,7 +4776,7 @@ ww \
4729
4776
  else
4730
4777
  export_init = menu_from_list_with_back(export.allow)
4731
4778
  command_result, exportable, new_lines = export_echo_with_code(
4732
- ["#{export.name}=#{Shellwords.escape(export_init)}"],
4779
+ [assign_key_value_in_bash(export.name, export_init)],
4733
4780
  export,
4734
4781
  force: force,
4735
4782
  silent: silent,
@@ -4739,6 +4786,7 @@ ww \
4739
4786
  end
4740
4787
 
4741
4788
  when :echo, UxActSource::ECHO
4789
+ wwp
4742
4790
  command_result, exportable, new_lines = export_echo_with_code(
4743
4791
  bash_script_lines,
4744
4792
  export,
@@ -4747,7 +4795,7 @@ ww \
4747
4795
  )
4748
4796
 
4749
4797
  when :edit, UxActSource::EDIT
4750
- output = nil
4798
+ output = ''
4751
4799
  begin
4752
4800
  loop do
4753
4801
  print "#{export.prompt} [#{export.default}]: "
@@ -4764,12 +4812,18 @@ ww \
4764
4812
  transformable = false
4765
4813
  end
4766
4814
 
4815
+ if exportable
4816
+ EnvInterface.set(export.name, output)
4817
+ new_lines << { name: export.name, force: force,
4818
+ text: output }
4819
+ end
4767
4820
  command_result = CommandResult.new(stdout: output)
4768
4821
 
4769
4822
  when :exec, UxActSource::EXEC
4770
4823
  command_result, exportable, new_lines = output_from_adhoc_bash_script_file(
4771
4824
  join_array_of_arrays(bash_script_lines, export.exec),
4772
- export
4825
+ export,
4826
+ force: force
4773
4827
  )
4774
4828
 
4775
4829
  else
@@ -4794,7 +4848,7 @@ ww \
4794
4848
  )
4795
4849
  command_result = nil
4796
4850
  exportable = true
4797
- force = false
4851
+ force = export.force.nil? ? false : export.force
4798
4852
  new_lines = []
4799
4853
  silent = true
4800
4854
  transformable = true
@@ -4815,11 +4869,12 @@ ww \
4815
4869
  bash_script_lines,
4816
4870
  %(printf '%s' "#{export.echo}")
4817
4871
  ),
4818
- export
4872
+ export,
4873
+ force: force
4819
4874
  )
4820
4875
  export_init = cr_echo.stdout.split("\n").first
4821
4876
  command_result, exportable, new_lines = export_echo_with_code(
4822
- ["#{export.name}=#{Shellwords.escape(export_init)}"],
4877
+ [assign_key_value_in_bash(export.name, export_init)],
4823
4878
  export,
4824
4879
  force: force,
4825
4880
  silent: silent,
@@ -4830,12 +4885,13 @@ ww \
4830
4885
  # extract first line from 'exec' output
4831
4886
  command_result, exportable, new_lines = output_from_adhoc_bash_script_file(
4832
4887
  join_array_of_arrays(bash_script_lines, export.exec),
4833
- export
4888
+ export,
4889
+ force: force
4834
4890
  )
4835
4891
  unless command_result.failure?
4836
4892
  export_init = command_result.stdout.split("\n").first
4837
4893
  command_result, exportable, new_lines = export_echo_with_code(
4838
- ["#{export.name}=#{Shellwords.escape(export_init)}"],
4894
+ [assign_key_value_in_bash(export.name, export_init)],
4839
4895
  export,
4840
4896
  force: force,
4841
4897
  silent: silent,
@@ -4847,7 +4903,7 @@ ww \
4847
4903
  # first item from 'allow' list
4848
4904
  export_init = export.allow.first
4849
4905
  command_result, exportable, new_lines = export_echo_with_code(
4850
- ["#{export.name}=#{Shellwords.escape(export_init)}"],
4906
+ [assign_key_value_in_bash(export.name, export_init)],
4851
4907
  export,
4852
4908
  force: force,
4853
4909
  silent: silent,
@@ -4869,18 +4925,20 @@ ww \
4869
4925
  force: force,
4870
4926
  silent: silent
4871
4927
  )
4928
+
4872
4929
  when :exec, UxActSource::EXEC
4873
4930
  raise unless export.exec.present?
4874
4931
 
4875
4932
  command_result, exportable, new_lines = output_from_adhoc_bash_script_file(
4876
4933
  join_array_of_arrays(bash_script_lines, export.exec),
4877
- export
4934
+ export,
4935
+ force: force
4878
4936
  )
4879
4937
 
4880
4938
  else
4881
4939
  export_init = export.init.to_s
4882
4940
  command_result, exportable, new_lines = export_echo_with_code(
4883
- ["#{export.name}=#{Shellwords.escape(export_init)}"],
4941
+ [assign_key_value_in_bash(export.name, export_init)],
4884
4942
  export,
4885
4943
  force: force,
4886
4944
  silent: silent,
@@ -5337,7 +5395,6 @@ ww \
5337
5395
  mdoc_menu_and_blocks_from_nested_files(
5338
5396
  @dml_link_state, source_id: source_id
5339
5397
  )
5340
- ww @dml_link_state####
5341
5398
  dump_delobj(@dml_blocks_in_file, @dml_menu_blocks, @dml_link_state)
5342
5399
  end
5343
5400
 
@@ -5541,6 +5598,16 @@ ww @dml_link_state####
5541
5598
  link_block_data[LinkKeys::FILE] || @delegate_object[:filename]
5542
5599
  end
5543
5600
  end
5601
+
5602
+ def safe_yaml_load(body)
5603
+ return {} unless body&.present?
5604
+
5605
+ YAML.load(body.to_s)
5606
+ rescue Psych::SyntaxError => err
5607
+ wwe 'YAML parsing error', { body: body, error: err.message }
5608
+ rescue StandardError => err
5609
+ wwe 'YAML loading error', { body: body, error: err.message }
5610
+ end
5544
5611
  end
5545
5612
 
5546
5613
  class HashDelegator < HashDelegatorParent
@@ -5884,7 +5951,7 @@ module MarkdownExec
5884
5951
  mdoc: @mdoc, selected: @selected, block_source: {}
5885
5952
  )
5886
5953
 
5887
- assert_equal ['code line', 'key=value'], result
5954
+ assert_equal ['code line', 'key="value"'], result
5888
5955
  end
5889
5956
  end
5890
5957
 
@@ -7,5 +7,5 @@ module MarkdownExec
7
7
  BIN_NAME = 'mde'
8
8
  GEM_NAME = 'markdown_exec'
9
9
  TAP_DEBUG = 'MDE_DEBUG'
10
- VERSION = '3.0.8'
10
+ VERSION = '3.1.0'
11
11
  end
data/lib/mdoc.rb CHANGED
@@ -178,11 +178,12 @@ module MarkdownExec
178
178
  collect_block_code_stdout(fcb)
179
179
  elsif [BlockType::OPTS].include? fcb.type
180
180
  fcb.body # entire body is returned to requesing block
181
+
181
182
  elsif [BlockType::LINK,
182
183
  BlockType::LOAD,
183
184
  BlockType::UX,
184
185
  BlockType::VARS].include? fcb.type
185
- nil
186
+ nil # Vars for all types are collected later
186
187
  elsif fcb[:chrome] # for Link blocks like History
187
188
  nil
188
189
  elsif fcb.type == BlockType::PORT