ast-template 7.0.0 → 7.1.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.
data/lib/ast/template.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "ast/merge"
4
- require_relative "template/version"
3
+ require 'fileutils'
4
+ require 'ast/merge'
5
+ require_relative 'template/version'
5
6
 
6
7
  module Ast
7
8
  module Template
@@ -17,17 +18,156 @@ module Ast
17
18
  SESSION_COMMAND_TRANSPORT_VERSION = 1
18
19
  SESSION_COMMAND_PAYLOAD_TRANSPORT_VERSION = 1
19
20
  SESSION_INVOCATION_TRANSPORT_VERSION = 1
21
+ README_FAMILY_LANGUAGE_ORDER = %w[go ruby rust typescript].freeze
22
+ README_FAMILY_LANGUAGE_LABELS = {
23
+ 'go' => 'Go',
24
+ 'ruby' => 'Ruby',
25
+ 'rust' => 'Rust',
26
+ 'typescript' => 'TypeScript'
27
+ }.freeze
20
28
 
21
29
  class << self
30
+ def readme_family_language_aliases(self_language, language_order = README_FAMILY_LANGUAGE_ORDER)
31
+ self_id = self_language.to_s
32
+ alternatives = language_order.map(&:to_s).reject { |language| language == self_id }
33
+ aliases = {
34
+ 'SELF_LANG' => README_FAMILY_LANGUAGE_LABELS.fetch(self_id, self_id),
35
+ 'SELF_LANG_ID' => self_id
36
+ }
37
+ alternatives.each_with_index do |language, index|
38
+ alias_prefix = "IMP_LANG#{index + 1}"
39
+ aliases[alias_prefix] = README_FAMILY_LANGUAGE_LABELS.fetch(language, language)
40
+ aliases["#{alias_prefix}_ID"] = language
41
+ end
42
+ aliases
43
+ end
44
+
45
+ def readme_family_token_values(family_metadata)
46
+ family = Ast::Merge.normalize_value(family_metadata || {})
47
+ self_entry = family.fetch(:self, {})
48
+ self_id = self_entry.fetch(:id).to_s
49
+ implementations = Array(family[:implementations]).each_with_object({}) do |entry, memo|
50
+ normalized_entry = Ast::Merge.normalize_value(entry)
51
+ memo[normalized_entry[:id].to_s] = normalized_entry
52
+ end
53
+ aliases = readme_family_language_aliases(self_id)
54
+ tokens = {
55
+ 'FAMILY_SECTION_HEADING' => family[:section_heading].to_s,
56
+ 'FAMILY_DESCRIPTION' => family[:description].to_s,
57
+ 'SELF_PACKAGE_ROOT' => self_entry[:package_root].to_s,
58
+ 'SELF_PACKAGE_MANAGER' => self_entry[:package_manager].to_s
59
+ }.merge(aliases)
60
+
61
+ README_FAMILY_LANGUAGE_ORDER.reject { |language| language == self_id }.each_with_index do |language, index|
62
+ implementation = implementations.fetch(language, {})
63
+ alias_prefix = "IMP_LANG#{index + 1}"
64
+ tokens["#{alias_prefix}_PACKAGE_ROOT"] = implementation[:package_root].to_s
65
+ tokens["#{alias_prefix}_PACKAGE_MANAGER"] = implementation[:package_manager].to_s
66
+ end
67
+
68
+ tokens
69
+ end
70
+
71
+ def render_readme_family_section(template_partial, family_metadata, config = nil)
72
+ replacements = readme_family_token_values(family_metadata).transform_keys { |key| "SM|#{key}" }
73
+ Ast::Merge.resolve_template_tokens(template_partial.to_s, replacements, config)
74
+ end
75
+
76
+ def apply_readme_family_section(template_partial, package_metadata, family_metadata, destination_content,
77
+ config = nil)
78
+ package_entry = Ast::Merge.normalize_value(package_metadata || {})
79
+ family = Ast::Merge.normalize_value(family_metadata || {})
80
+ rendered_section = render_readme_family_section(template_partial, family, config).rstrip
81
+ base_content = if destination_content.nil?
82
+ "# #{package_entry[:name]}\n\n#{package_entry[:summary]}\n"
83
+ else
84
+ destination_content.to_s
85
+ end
86
+ content = replace_or_insert_markdown_heading_section(
87
+ base_content,
88
+ family[:section_heading].to_s,
89
+ 2,
90
+ rendered_section
91
+ )
92
+ {
93
+ content: content,
94
+ changed: content != base_content
95
+ }
96
+ end
97
+
98
+ def apply_readme_family_sections_to_package_directories(root, template_partial, packages, config = nil)
99
+ run_readme_family_sections_for_package_directories(root, template_partial, packages, true, config)
100
+ end
101
+
102
+ def plan_readme_family_sections_for_package_directories(root, template_partial, packages, config = nil)
103
+ run_readme_family_sections_for_package_directories(root, template_partial, packages, false, config)
104
+ end
105
+
106
+ def run_readme_family_section_command(command, config = nil)
107
+ normalized = Ast::Merge.normalize_value(command || {})
108
+ mode = normalized.fetch(:mode, 'plan').to_s
109
+ write_changes = %w[apply reapply].include?(mode)
110
+ {
111
+ profile_name: normalized.fetch(:profile_name, '').to_s,
112
+ mode: mode,
113
+ runner: run_readme_family_sections_for_package_directories(
114
+ normalized.fetch(:root, '').to_s,
115
+ normalized.fetch(:template_partial, '').to_s,
116
+ Array(normalized[:packages]),
117
+ write_changes,
118
+ config
119
+ )
120
+ }
121
+ end
122
+
123
+ def run_readme_family_sections_for_package_directories(root, template_partial, packages, write_changes,
124
+ config = nil)
125
+ report = {
126
+ package_count: packages.length,
127
+ changed_count: 0,
128
+ created_count: 0,
129
+ entries: []
130
+ }
131
+ packages.each do |package_entry|
132
+ normalized_entry = Ast::Merge.normalize_value(package_entry)
133
+ readme_path = normalized_entry.fetch(:readme_path).to_s
134
+ full_path = File.join(root.to_s, readme_path.split('/'))
135
+ created = !File.exist?(full_path)
136
+ destination_content = created ? nil : File.read(full_path)
137
+ application = apply_readme_family_section(
138
+ template_partial,
139
+ normalized_entry.fetch(:package, {}),
140
+ normalized_entry.fetch(:family, {}),
141
+ destination_content,
142
+ config
143
+ )
144
+ if application[:changed]
145
+ if write_changes
146
+ FileUtils.mkdir_p(File.dirname(full_path))
147
+ File.write(full_path, application[:content])
148
+ end
149
+ report[:changed_count] += 1
150
+ report[:created_count] += 1 if created
151
+ end
152
+ report[:entries] << {
153
+ id: normalized_entry.fetch(:id).to_s,
154
+ readme_path: readme_path,
155
+ changed: application[:changed],
156
+ created: created && application[:changed]
157
+ }
158
+ end
159
+ report
160
+ end
161
+
22
162
  def merge_prepared_content_from_registry(registry, entry)
23
- family = entry.dig(:classification, :family) || entry.dig("classification", "family")
163
+ family = entry.dig(:classification, :family) || entry.dig('classification', 'family')
24
164
  adapter = registry[family.to_s]
25
165
  unless adapter
26
166
  return {
27
167
  ok: false,
28
168
  diagnostics: [{
29
- severity: "error",
30
- category: "configuration_error",
169
+ severity: 'error',
170
+ category: 'configuration_error',
31
171
  message: "missing family adapter for #{family}"
32
172
  }],
33
173
  policies: []
@@ -48,7 +188,7 @@ module Ast
48
188
  {
49
189
  mode: normalized_mode,
50
190
  adapter_families: registered_adapter_families(registry),
51
- diagnostics: Array(result&.dig(:apply_result, :diagnostics) || result&.dig("apply_result", "diagnostics")),
191
+ diagnostics: Array(result&.dig(:apply_result, :diagnostics) || result&.dig('apply_result', 'diagnostics')),
52
192
  runner_report: Ast::Merge.report_template_directory_runner(entries, result)
53
193
  }
54
194
  end
@@ -60,31 +200,54 @@ module Ast
60
200
  end
61
201
 
62
202
  registry = {}
63
- if include_family.call("markdown")
203
+ if include_family.call('markdown')
64
204
  begin
65
- require "markdown-merge"
66
- registry["markdown"] = lambda do |entry|
67
- Markdown::Merge.merge_markdown(entry[:prepared_template_content], entry[:destination_content], "markdown")
205
+ require 'markly-merge'
206
+ registry['markdown'] = lambda do |entry|
207
+ {
208
+ ok: true,
209
+ output: Markly::Merge::SmartMerger.new(
210
+ entry[:prepared_template_content],
211
+ entry[:destination_content],
212
+ add_template_only_nodes: true
213
+ ).merge,
214
+ diagnostics: [],
215
+ policies: []
216
+ }
68
217
  end
69
218
  rescue LoadError
219
+ begin
220
+ require 'markdown-merge'
221
+ registry['markdown'] = lambda do |entry|
222
+ Markdown::Merge.merge_markdown(entry[:prepared_template_content], entry[:destination_content], 'markdown')
223
+ end
224
+ rescue LoadError
225
+ end
70
226
  end
71
227
  end
72
- if include_family.call("toml")
228
+ if include_family.call('toml')
73
229
  begin
74
- require "toml-merge"
75
- registry["toml"] = lambda do |entry|
76
- Toml::Merge.merge_toml(entry[:prepared_template_content], entry[:destination_content], "toml")
230
+ require 'toml-merge'
231
+ registry['toml'] = lambda do |entry|
232
+ Toml::Merge.merge_toml(entry[:prepared_template_content], entry[:destination_content], 'toml')
77
233
  end
78
234
  rescue LoadError
79
235
  end
80
236
  end
81
- if include_family.call("ruby")
237
+ if include_family.call('ruby')
82
238
  begin
83
- require "ruby-merge"
84
- registry["ruby"] = lambda do |entry|
85
- Ruby::Merge.merge_ruby(entry[:prepared_template_content], entry[:destination_content], "ruby")
239
+ require 'prism-merge'
240
+ registry['ruby'] = lambda do |entry|
241
+ Prism::Merge.merge_ruby(entry[:prepared_template_content], entry[:destination_content], 'ruby')
86
242
  end
87
243
  rescue LoadError
244
+ begin
245
+ require 'ruby-merge'
246
+ registry['ruby'] = lambda do |entry|
247
+ Ruby::Merge.merge_ruby(entry[:prepared_template_content], entry[:destination_content], 'ruby')
248
+ end
249
+ rescue LoadError
250
+ end
88
251
  end
89
252
  end
90
253
 
@@ -102,7 +265,7 @@ module Ast
102
265
  end
103
266
 
104
267
  def plan_template_directory_session_from_directories(template_root, destination_root,
105
- context, default_strategy, overrides, replacements, config = nil)
268
+ context, default_strategy, overrides, replacements, config = nil)
106
269
  entries = Ast::Merge.plan_template_tree_execution_from_directories(
107
270
  template_root,
108
271
  destination_root,
@@ -116,7 +279,7 @@ module Ast
116
279
  end
117
280
 
118
281
  def apply_template_directory_session_to_directory(template_root, destination_root,
119
- context, default_strategy, overrides, replacements, config = nil, &merge_callback)
282
+ context, default_strategy, overrides, replacements, config = nil, &merge_callback)
120
283
  result = Ast::Merge.apply_template_tree_execution_to_directory(
121
284
  template_root,
122
285
  destination_root,
@@ -131,7 +294,7 @@ module Ast
131
294
  end
132
295
 
133
296
  def reapply_template_directory_session_to_directory(template_root, destination_root,
134
- context, default_strategy, overrides, replacements, config = nil, &merge_callback)
297
+ context, default_strategy, overrides, replacements, config = nil, &merge_callback)
135
298
  result = Ast::Merge.apply_template_tree_execution_to_directory(
136
299
  template_root,
137
300
  destination_root,
@@ -146,7 +309,7 @@ module Ast
146
309
  end
147
310
 
148
311
  def apply_template_directory_session_with_registry_to_directory(template_root, destination_root,
149
- context, default_strategy, overrides, replacements, registry, config = nil)
312
+ context, default_strategy, overrides, replacements, registry, config = nil)
150
313
  result = Ast::Merge.apply_template_tree_execution_to_directory(
151
314
  template_root,
152
315
  destination_root,
@@ -162,7 +325,7 @@ module Ast
162
325
  end
163
326
 
164
327
  def apply_template_directory_session_with_default_registry_to_directory(template_root, destination_root,
165
- context, default_strategy, overrides, replacements, allowed_families = nil, config = nil)
328
+ context, default_strategy, overrides, replacements, allowed_families = nil, config = nil)
166
329
  registry = default_family_merge_adapter_registry(allowed_families)
167
330
  apply_template_directory_session_with_registry_to_directory(
168
331
  template_root,
@@ -178,9 +341,9 @@ module Ast
178
341
 
179
342
  def required_families(entries)
180
343
  Array(entries).filter_map do |entry|
181
- next unless (entry[:execution_action] || entry["execution_action"]).to_s == "merge_prepared_content"
344
+ next unless (entry[:execution_action] || entry['execution_action']).to_s == 'merge_prepared_content'
182
345
 
183
- entry.dig(:classification, :family) || entry.dig("classification", "family")
346
+ entry.dig(:classification, :family) || entry.dig('classification', 'family')
184
347
  end.uniq.sort
185
348
  end
186
349
 
@@ -197,7 +360,7 @@ module Ast
197
360
  end
198
361
 
199
362
  def report_adapter_capabilities_from_directories(template_root, destination_root,
200
- context, default_strategy, overrides, replacements, registry, config = nil)
363
+ context, default_strategy, overrides, replacements, registry, config = nil)
201
364
  entries = Ast::Merge.plan_template_tree_execution_from_directories(
202
365
  template_root,
203
366
  destination_root,
@@ -211,7 +374,7 @@ module Ast
211
374
  end
212
375
 
213
376
  def report_default_adapter_capabilities_from_directories(template_root, destination_root,
214
- context, default_strategy, overrides, replacements, allowed_families = nil, config = nil)
377
+ context, default_strategy, overrides, replacements, allowed_families = nil, config = nil)
215
378
  report_adapter_capabilities_from_directories(
216
379
  template_root,
217
380
  destination_root,
@@ -232,7 +395,7 @@ module Ast
232
395
  end
233
396
 
234
397
  def plan_template_directory_session_envelope_from_directories(template_root, destination_root,
235
- context, default_strategy, overrides, replacements, allowed_families = nil, config = nil)
398
+ context, default_strategy, overrides, replacements, allowed_families = nil, config = nil)
236
399
  report_template_directory_session_envelope(
237
400
  plan_template_directory_session_from_directories(
238
401
  template_root,
@@ -257,7 +420,7 @@ module Ast
257
420
  end
258
421
 
259
422
  def apply_template_directory_session_envelope_with_default_registry_to_directory(template_root, destination_root,
260
- context, default_strategy, overrides, replacements, allowed_families = nil, config = nil)
423
+ context, default_strategy, overrides, replacements, allowed_families = nil, config = nil)
261
424
  report_template_directory_session_envelope(
262
425
  apply_template_directory_session_with_default_registry_to_directory(
263
426
  template_root,
@@ -283,84 +446,92 @@ module Ast
283
446
  end
284
447
 
285
448
  def report_template_directory_session_status(envelope)
286
- session_report = envelope[:session_report] || envelope["session_report"] || {}
287
- adapter_capabilities = envelope[:adapter_capabilities] || envelope["adapter_capabilities"] || {}
288
- runner_report = session_report[:runner_report] || session_report["runner_report"] || {}
289
- plan_report = runner_report[:plan_report] || runner_report["plan_report"] || {}
290
- entries = Array(plan_report[:entries] || plan_report["entries"])
291
- plan_summary = plan_report[:summary] || plan_report["summary"] || {}
292
- apply_report = runner_report[:apply_report] || runner_report["apply_report"] || {}
293
- apply_entries = Array(apply_report[:entries] || apply_report["entries"])
294
- apply_summary = apply_report[:summary] || apply_report["summary"] || {}
449
+ session_report = envelope[:session_report] || envelope['session_report'] || {}
450
+ adapter_capabilities = envelope[:adapter_capabilities] || envelope['adapter_capabilities'] || {}
451
+ runner_report = session_report[:runner_report] || session_report['runner_report'] || {}
452
+ plan_report = runner_report[:plan_report] || runner_report['plan_report'] || {}
453
+ entries = Array(plan_report[:entries] || plan_report['entries'])
454
+ plan_summary = plan_report[:summary] || plan_report['summary'] || {}
455
+ apply_report = runner_report[:apply_report] || runner_report['apply_report'] || {}
456
+ apply_entries = Array(apply_report[:entries] || apply_report['entries'])
457
+ apply_summary = apply_report[:summary] || apply_report['summary'] || {}
295
458
  blocked_paths = (entries + apply_entries).filter_map do |entry|
296
- status = entry[:status] || entry["status"]
297
- destination_path = entry[:destination_path] || entry["destination_path"]
298
- destination_path if status.to_s == "blocked" && destination_path
459
+ status = entry[:status] || entry['status']
460
+ destination_path = entry[:destination_path] || entry['destination_path']
461
+ destination_path if status.to_s == 'blocked' && destination_path
299
462
  end.uniq.sort
300
463
  missing_families = Array(
301
- adapter_capabilities[:missing_families] || adapter_capabilities["missing_families"]
464
+ adapter_capabilities[:missing_families] || adapter_capabilities['missing_families']
302
465
  ).map(&:to_s).sort
303
466
 
304
467
  {
305
- mode: (session_report[:mode] || session_report["mode"]).to_s,
306
- ready: !!(adapter_capabilities[:ready] || adapter_capabilities["ready"]) && blocked_paths.empty?,
468
+ mode: (session_report[:mode] || session_report['mode']).to_s,
469
+ ready: !(adapter_capabilities[:ready] || adapter_capabilities['ready']).nil? && blocked_paths.empty?,
307
470
  missing_families: missing_families,
308
471
  blocked_paths: blocked_paths,
309
- planned_write_count: plan_summary.fetch(:create, plan_summary.fetch("create", 0)) +
310
- plan_summary.fetch(:update, plan_summary.fetch("update", 0)),
311
- written_count: apply_summary.fetch(:written, apply_summary.fetch("written", 0))
472
+ planned_write_count: plan_summary.fetch(:create, plan_summary.fetch('create', 0)) +
473
+ plan_summary.fetch(:update, plan_summary.fetch('update', 0)),
474
+ written_count: apply_summary.fetch(:written, apply_summary.fetch('written', 0))
312
475
  }
313
476
  end
314
477
 
315
478
  def template_directory_session_status_envelope(status)
316
479
  {
317
- kind: "template_directory_session_status",
480
+ kind: 'template_directory_session_status',
318
481
  version: SESSION_STATUS_TRANSPORT_VERSION,
319
482
  status: deep_dup(status)
320
483
  }
321
484
  end
322
485
 
323
486
  def import_template_directory_session_status_envelope(envelope)
324
- return [nil, { category: "kind_mismatch", message: "expected template_directory_session_status envelope kind." }] unless envelope[:kind] == "template_directory_session_status"
325
- return [nil, { category: "unsupported_version", message: "unsupported template_directory_session_status envelope version #{envelope[:version]}." }] unless envelope[:version] == SESSION_STATUS_TRANSPORT_VERSION
487
+ unless envelope[:kind] == 'template_directory_session_status'
488
+ return [nil,
489
+ { category: 'kind_mismatch',
490
+ message: 'expected template_directory_session_status envelope kind.' }]
491
+ end
492
+ unless envelope[:version] == SESSION_STATUS_TRANSPORT_VERSION
493
+ return [nil,
494
+ { category: 'unsupported_version',
495
+ message: "unsupported template_directory_session_status envelope version #{envelope[:version]}." }]
496
+ end
326
497
 
327
498
  [deep_dup(envelope[:status]), nil]
328
499
  end
329
500
 
330
501
  def report_template_directory_session_diagnostics(mode, entries, adapter_capabilities, result = nil)
331
502
  missing_families = Array(
332
- adapter_capabilities[:missing_families] || adapter_capabilities["missing_families"]
503
+ adapter_capabilities[:missing_families] || adapter_capabilities['missing_families']
333
504
  ).map(&:to_s)
334
505
  blocked_apply_paths = Array(
335
- result&.dig(:apply_report, :entries) || result&.dig("apply_report", "entries")
506
+ result&.dig(:apply_report, :entries) || result&.dig('apply_report', 'entries')
336
507
  ).filter_map do |entry|
337
- destination_path = entry[:destination_path] || entry["destination_path"]
338
- status = entry[:status] || entry["status"]
339
- destination_path if status.to_s == "blocked" && destination_path
508
+ destination_path = entry[:destination_path] || entry['destination_path']
509
+ status = entry[:status] || entry['status']
510
+ destination_path if status.to_s == 'blocked' && destination_path
340
511
  end
341
512
 
342
513
  diagnostics = Array(entries).flat_map do |entry|
343
- path = entry[:destination_path] || entry["destination_path"] ||
344
- entry[:logical_destination_path] || entry["logical_destination_path"]
345
- family = entry.dig(:classification, :family) || entry.dig("classification", "family")
514
+ path = entry[:destination_path] || entry['destination_path'] ||
515
+ entry[:logical_destination_path] || entry['logical_destination_path']
516
+ family = entry.dig(:classification, :family) || entry.dig('classification', 'family')
346
517
  result_entries = []
347
- block_reason = entry[:block_reason] || entry["block_reason"]
348
- if (entry[:blocked] || entry["blocked"]) && block_reason.to_s == "unresolved_tokens"
518
+ block_reason = entry[:block_reason] || entry['block_reason']
519
+ if (entry[:blocked] || entry['blocked']) && block_reason.to_s == 'unresolved_tokens'
349
520
  result_entries << {
350
- severity: "error",
351
- category: "configuration_error",
352
- reason: "unresolved_tokens",
521
+ severity: 'error',
522
+ category: 'configuration_error',
523
+ reason: 'unresolved_tokens',
353
524
  path: path,
354
525
  message: "unresolved template tokens block #{path}"
355
526
  }
356
527
  end
357
- if (entry[:execution_action] || entry["execution_action"]).to_s == "merge_prepared_content" &&
358
- missing_families.include?(family.to_s) &&
359
- (result.nil? || blocked_apply_paths.empty? || blocked_apply_paths.include?(path))
528
+ if (entry[:execution_action] || entry['execution_action']).to_s == 'merge_prepared_content' &&
529
+ missing_families.include?(family.to_s) &&
530
+ (result.nil? || blocked_apply_paths.empty? || blocked_apply_paths.include?(path))
360
531
  result_entries << {
361
- severity: "error",
362
- category: "configuration_error",
363
- reason: "missing_family_adapter",
532
+ severity: 'error',
533
+ category: 'configuration_error',
534
+ reason: 'missing_family_adapter',
364
535
  path: path,
365
536
  family: family.to_s,
366
537
  message: "missing family adapter for #{family} blocks #{path}"
@@ -378,21 +549,29 @@ module Ast
378
549
 
379
550
  def template_directory_session_diagnostics_envelope(diagnostics)
380
551
  {
381
- kind: "template_directory_session_diagnostics",
552
+ kind: 'template_directory_session_diagnostics',
382
553
  version: SESSION_DIAGNOSTICS_TRANSPORT_VERSION,
383
554
  diagnostics: deep_dup(diagnostics)
384
555
  }
385
556
  end
386
557
 
387
558
  def import_template_directory_session_diagnostics_envelope(envelope)
388
- return [nil, { category: "kind_mismatch", message: "expected template_directory_session_diagnostics envelope kind." }] unless envelope[:kind] == "template_directory_session_diagnostics"
389
- return [nil, { category: "unsupported_version", message: "unsupported template_directory_session_diagnostics envelope version #{envelope[:version]}." }] unless envelope[:version] == SESSION_DIAGNOSTICS_TRANSPORT_VERSION
559
+ unless envelope[:kind] == 'template_directory_session_diagnostics'
560
+ return [nil,
561
+ { category: 'kind_mismatch',
562
+ message: 'expected template_directory_session_diagnostics envelope kind.' }]
563
+ end
564
+ unless envelope[:version] == SESSION_DIAGNOSTICS_TRANSPORT_VERSION
565
+ return [nil,
566
+ { category: 'unsupported_version',
567
+ message: "unsupported template_directory_session_diagnostics envelope version #{envelope[:version]}." }]
568
+ end
390
569
 
391
570
  [deep_dup(envelope[:diagnostics]), nil]
392
571
  end
393
572
 
394
573
  def plan_template_directory_session_diagnostics_from_directories(template_root, destination_root,
395
- context, default_strategy, overrides, replacements, allowed_families = nil, config = nil)
574
+ context, default_strategy, overrides, replacements, allowed_families = nil, config = nil)
396
575
  entries = Ast::Merge.plan_template_tree_execution_from_directories(
397
576
  template_root,
398
577
  destination_root,
@@ -410,7 +589,7 @@ module Ast
410
589
  end
411
590
 
412
591
  def apply_template_directory_session_diagnostics_with_default_registry_to_directory(template_root, destination_root,
413
- context, default_strategy, overrides, replacements, allowed_families = nil, config = nil)
592
+ context, default_strategy, overrides, replacements, allowed_families = nil, config = nil)
414
593
  registry = default_family_merge_adapter_registry(allowed_families)
415
594
  result = Ast::Merge.apply_template_tree_execution_to_directory(
416
595
  template_root,
@@ -440,7 +619,7 @@ module Ast
440
619
  end
441
620
 
442
621
  def plan_template_directory_session_outcome_from_directories(template_root, destination_root,
443
- context, default_strategy, overrides, replacements, allowed_families = nil, config = nil)
622
+ context, default_strategy, overrides, replacements, allowed_families = nil, config = nil)
444
623
  report_template_directory_session_outcome(
445
624
  plan_template_directory_session_from_directories(
446
625
  template_root,
@@ -477,7 +656,7 @@ module Ast
477
656
  end
478
657
 
479
658
  def apply_template_directory_session_outcome_with_default_registry_to_directory(template_root, destination_root,
480
- context, default_strategy, overrides, replacements, allowed_families = nil, config = nil)
659
+ context, default_strategy, overrides, replacements, allowed_families = nil, config = nil)
481
660
  registry = default_family_merge_adapter_registry(allowed_families)
482
661
  result = Ast::Merge.apply_template_tree_execution_to_directory(
483
662
  template_root,
@@ -507,7 +686,7 @@ module Ast
507
686
  end
508
687
 
509
688
  def reapply_template_directory_session_outcome_with_default_registry_to_directory(template_root, destination_root,
510
- context, default_strategy, overrides, replacements, allowed_families = nil, config = nil)
689
+ context, default_strategy, overrides, replacements, allowed_families = nil, config = nil)
511
690
  registry = default_family_merge_adapter_registry(allowed_families)
512
691
  result = Ast::Merge.apply_template_tree_execution_to_directory(
513
692
  template_root,
@@ -537,9 +716,9 @@ module Ast
537
716
  end
538
717
 
539
718
  def run_template_directory_session_with_default_registry_to_directory(mode, template_root, destination_root,
540
- context, default_strategy, overrides, replacements, allowed_families = nil, config = nil)
719
+ context, default_strategy, overrides, replacements, allowed_families = nil, config = nil)
541
720
  case mode.to_s
542
- when "plan"
721
+ when 'plan'
543
722
  plan_template_directory_session_outcome_from_directories(
544
723
  template_root,
545
724
  destination_root,
@@ -550,7 +729,7 @@ module Ast
550
729
  allowed_families,
551
730
  config
552
731
  )
553
- when "apply"
732
+ when 'apply'
554
733
  apply_template_directory_session_outcome_with_default_registry_to_directory(
555
734
  template_root,
556
735
  destination_root,
@@ -561,7 +740,7 @@ module Ast
561
740
  allowed_families,
562
741
  config
563
742
  )
564
- when "reapply"
743
+ when 'reapply'
565
744
  reapply_template_directory_session_outcome_with_default_registry_to_directory(
566
745
  template_root,
567
746
  destination_root,
@@ -587,40 +766,40 @@ module Ast
587
766
  end
588
767
  normalized = deep_dup(request[:resolved_options])
589
768
  run_template_directory_session_with_default_registry_to_directory(
590
- normalized[:mode] || normalized["mode"],
591
- normalized[:template_root] || normalized["template_root"],
592
- normalized[:destination_root] || normalized["destination_root"],
593
- normalized[:context] || normalized["context"] || {},
594
- normalized[:default_strategy] || normalized["default_strategy"],
595
- normalized[:overrides] || normalized["overrides"] || [],
596
- normalized[:replacements] || normalized["replacements"] || {},
597
- normalized[:allowed_families] || normalized["allowed_families"],
598
- normalized[:config] || normalized["config"]
769
+ normalized[:mode] || normalized['mode'],
770
+ normalized[:template_root] || normalized['template_root'],
771
+ normalized[:destination_root] || normalized['destination_root'],
772
+ normalized[:context] || normalized['context'] || {},
773
+ normalized[:default_strategy] || normalized['default_strategy'],
774
+ normalized[:overrides] || normalized['overrides'] || [],
775
+ normalized[:replacements] || normalized['replacements'] || {},
776
+ normalized[:allowed_families] || normalized['allowed_families'],
777
+ normalized[:config] || normalized['config']
599
778
  )
600
779
  end
601
780
 
602
781
  def report_template_directory_session_options_configuration(options)
603
782
  normalized = deep_dup(options)
604
783
  diagnostics = []
605
- unless (normalized[:destination_root] || normalized["destination_root"]).to_s.length.positive?
784
+ unless (normalized[:destination_root] || normalized['destination_root']).to_s.length.positive?
606
785
  diagnostics << {
607
- severity: "error",
608
- category: "configuration_error",
609
- reason: "missing_destination_root",
610
- message: "missing destination_root for template session"
786
+ severity: 'error',
787
+ category: 'configuration_error',
788
+ reason: 'missing_destination_root',
789
+ message: 'missing destination_root for template session'
611
790
  }
612
791
  end
613
- unless (normalized[:template_root] || normalized["template_root"]).to_s.length.positive?
792
+ unless (normalized[:template_root] || normalized['template_root']).to_s.length.positive?
614
793
  diagnostics << {
615
- severity: "error",
616
- category: "configuration_error",
617
- reason: "missing_template_root",
618
- message: "missing template_root for template session"
794
+ severity: 'error',
795
+ category: 'configuration_error',
796
+ reason: 'missing_template_root',
797
+ message: 'missing template_root for template session'
619
798
  }
620
799
  end
621
800
  diagnostics.sort_by! { |entry| entry[:reason] }
622
801
  {
623
- mode: normalize_session_mode(normalized[:mode] || normalized["mode"]),
802
+ mode: normalize_session_mode(normalized[:mode] || normalized['mode']),
624
803
  ready: diagnostics.empty?,
625
804
  diagnostics: diagnostics
626
805
  }
@@ -630,7 +809,7 @@ module Ast
630
809
  normalized = deep_dup(options)
631
810
  configuration = report_template_directory_session_options_configuration(normalized)
632
811
  {
633
- request_kind: "options",
812
+ request_kind: 'options',
634
813
  mode: configuration[:mode],
635
814
  ready: configuration[:ready],
636
815
  diagnostics: deep_dup(configuration[:diagnostics]),
@@ -644,13 +823,13 @@ module Ast
644
823
  diagnostics = report_template_directory_session_options_configuration(normalized_overrides)[:diagnostics]
645
824
  profile = normalized_profiles[profile_name.to_s] || normalized_profiles[profile_name.to_sym]
646
825
  mode = normalize_session_mode(
647
- normalized_overrides[:mode] || normalized_overrides["mode"] || profile&.[](:mode) || profile&.[]("mode")
826
+ normalized_overrides[:mode] || normalized_overrides['mode'] || profile&.[](:mode) || profile&.[]('mode')
648
827
  )
649
828
  unless profile
650
829
  diagnostics << {
651
- severity: "error",
652
- category: "configuration_error",
653
- reason: "missing_profile",
830
+ severity: 'error',
831
+ category: 'configuration_error',
832
+ reason: 'missing_profile',
654
833
  message: "unknown template session profile: #{profile_name}"
655
834
  }
656
835
  end
@@ -665,14 +844,16 @@ module Ast
665
844
  def report_template_directory_session_profile_request(profiles, profile_name, overrides)
666
845
  configuration = report_template_directory_session_profile_configuration(profiles, profile_name, overrides)
667
846
  {
668
- request_kind: "profile",
847
+ request_kind: 'profile',
669
848
  profile_name: profile_name.to_s,
670
849
  mode: configuration[:mode],
671
850
  ready: configuration[:ready],
672
851
  diagnostics: deep_dup(configuration[:diagnostics]),
673
- resolved_options: configuration[:ready] ? compact_session_request_options(
674
- resolve_template_directory_session_options(profiles, profile_name, overrides)
675
- ) : nil
852
+ resolved_options: if configuration[:ready]
853
+ compact_session_request_options(
854
+ resolve_template_directory_session_options(profiles, profile_name, overrides)
855
+ )
856
+ end
676
857
  }
677
858
  end
678
859
 
@@ -693,24 +874,32 @@ module Ast
693
874
 
694
875
  def template_directory_session_outcome_envelope(outcome)
695
876
  {
696
- kind: "template_directory_session_outcome",
877
+ kind: 'template_directory_session_outcome',
697
878
  version: SESSION_OUTCOME_TRANSPORT_VERSION,
698
879
  outcome: deep_dup(outcome)
699
880
  }
700
881
  end
701
882
 
702
883
  def import_template_directory_session_outcome_envelope(envelope)
703
- return [nil, { category: "kind_mismatch", message: "expected template_directory_session_outcome envelope kind." }] unless envelope[:kind] == "template_directory_session_outcome"
704
- return [nil, { category: "unsupported_version", message: "unsupported template_directory_session_outcome envelope version #{envelope[:version]}." }] unless envelope[:version] == SESSION_OUTCOME_TRANSPORT_VERSION
884
+ unless envelope[:kind] == 'template_directory_session_outcome'
885
+ return [nil,
886
+ { category: 'kind_mismatch',
887
+ message: 'expected template_directory_session_outcome envelope kind.' }]
888
+ end
889
+ unless envelope[:version] == SESSION_OUTCOME_TRANSPORT_VERSION
890
+ return [nil,
891
+ { category: 'unsupported_version',
892
+ message: "unsupported template_directory_session_outcome envelope version #{envelope[:version]}." }]
893
+ end
705
894
 
706
895
  [deep_dup(envelope[:outcome]), nil]
707
896
  end
708
897
 
709
898
  def run_template_directory_session_request(request)
710
899
  normalized = deep_dup(request)
711
- unless normalized[:ready] || normalized["ready"]
712
- mode = normalized[:mode] || normalized["mode"]
713
- diagnostics = normalized[:diagnostics] || normalized["diagnostics"] || []
900
+ unless normalized[:ready] || normalized['ready']
901
+ mode = normalized[:mode] || normalized['mode']
902
+ diagnostics = normalized[:diagnostics] || normalized['diagnostics'] || []
714
903
  return report_template_directory_session_configuration_outcome(
715
904
  mode,
716
905
  { mode: mode, ready: false, diagnostics: diagnostics }
@@ -718,130 +907,151 @@ module Ast
718
907
  end
719
908
 
720
909
  run_template_directory_session_with_options(
721
- normalized[:resolved_options] || normalized["resolved_options"]
910
+ normalized[:resolved_options] || normalized['resolved_options']
722
911
  )
723
912
  end
724
913
 
725
914
  def template_directory_session_request_envelope(request)
726
915
  {
727
- kind: "template_directory_session_request",
916
+ kind: 'template_directory_session_request',
728
917
  version: SESSION_REQUEST_TRANSPORT_VERSION,
729
918
  request: deep_dup(request)
730
919
  }
731
920
  end
732
921
 
733
922
  def import_template_directory_session_request_envelope(envelope)
734
- return [nil, { category: "kind_mismatch", message: "expected template_directory_session_request envelope kind." }] unless envelope[:kind] == "template_directory_session_request"
735
- return [nil, { category: "unsupported_version", message: "unsupported template_directory_session_request envelope version #{envelope[:version]}." }] unless envelope[:version] == SESSION_REQUEST_TRANSPORT_VERSION
923
+ unless envelope[:kind] == 'template_directory_session_request'
924
+ return [nil,
925
+ { category: 'kind_mismatch',
926
+ message: 'expected template_directory_session_request envelope kind.' }]
927
+ end
928
+ unless envelope[:version] == SESSION_REQUEST_TRANSPORT_VERSION
929
+ return [nil,
930
+ { category: 'unsupported_version',
931
+ message: "unsupported template_directory_session_request envelope version #{envelope[:version]}." }]
932
+ end
736
933
 
737
934
  [deep_dup(envelope[:request]), nil]
738
935
  end
739
936
 
740
937
  def run_template_directory_session_runner_request(request, profiles = {})
741
938
  normalized = deep_dup(request)
742
- request_kind = normalized[:request_kind] || normalized["request_kind"]
743
- if request_kind.to_s == "profile"
939
+ request_kind = normalized[:request_kind] || normalized['request_kind']
940
+ if request_kind.to_s == 'profile'
744
941
  return run_template_directory_session_request(
745
942
  report_template_directory_session_profile_request(
746
943
  profiles,
747
- normalized[:profile_name] || normalized["profile_name"],
748
- normalized[:overrides] || normalized["overrides"] || {}
944
+ normalized[:profile_name] || normalized['profile_name'],
945
+ normalized[:overrides] || normalized['overrides'] || {}
749
946
  )
750
947
  )
751
948
  end
752
949
 
753
950
  run_template_directory_session_request(
754
951
  report_template_directory_session_options_request(
755
- normalized[:options] || normalized["options"] || {}
952
+ normalized[:options] || normalized['options'] || {}
756
953
  )
757
954
  )
758
955
  end
759
956
 
760
957
  def template_directory_session_runner_request_envelope(request)
761
958
  {
762
- kind: "template_directory_session_runner_request",
959
+ kind: 'template_directory_session_runner_request',
763
960
  version: SESSION_RUNNER_REQUEST_TRANSPORT_VERSION,
764
961
  request: deep_dup(request)
765
962
  }
766
963
  end
767
964
 
768
965
  def import_template_directory_session_runner_request_envelope(envelope)
769
- return [nil, { category: "kind_mismatch", message: "expected template_directory_session_runner_request envelope kind." }] unless envelope[:kind] == "template_directory_session_runner_request"
770
- return [nil, { category: "unsupported_version", message: "unsupported template_directory_session_runner_request envelope version #{envelope[:version]}." }] unless envelope[:version] == SESSION_RUNNER_REQUEST_TRANSPORT_VERSION
966
+ unless envelope[:kind] == 'template_directory_session_runner_request'
967
+ return [nil,
968
+ { category: 'kind_mismatch',
969
+ message: 'expected template_directory_session_runner_request envelope kind.' }]
970
+ end
971
+ unless envelope[:version] == SESSION_RUNNER_REQUEST_TRANSPORT_VERSION
972
+ return [nil,
973
+ { category: 'unsupported_version',
974
+ message: "unsupported template_directory_session_runner_request envelope version #{envelope[:version]}." }]
975
+ end
771
976
 
772
977
  [deep_dup(envelope[:request]), nil]
773
978
  end
774
979
 
775
980
  def report_template_directory_session_runner_input(input)
776
981
  normalized = deep_dup(input)
777
- request_kind = (normalized[:request_kind] || normalized["request_kind"]).to_s
982
+ request_kind = (normalized[:request_kind] || normalized['request_kind']).to_s
778
983
  options = {
779
- mode: normalized[:mode] || normalized["mode"],
780
- template_root: normalized[:template_root] || normalized["template_root"],
781
- destination_root: normalized[:destination_root] || normalized["destination_root"],
782
- context: normalized[:context] || normalized["context"] || {},
783
- default_strategy: normalized[:default_strategy] || normalized["default_strategy"] || "merge",
784
- overrides: normalized[:overrides] || normalized["overrides"] || [],
785
- replacements: normalized[:replacements] || normalized["replacements"] || {},
786
- allowed_families: normalized.key?(:allowed_families) || normalized.key?("allowed_families") ?
787
- (normalized[:allowed_families] || normalized["allowed_families"]) : nil
984
+ mode: normalized[:mode] || normalized['mode'],
985
+ template_root: normalized[:template_root] || normalized['template_root'],
986
+ destination_root: normalized[:destination_root] || normalized['destination_root'],
987
+ context: normalized[:context] || normalized['context'] || {},
988
+ default_strategy: normalized[:default_strategy] || normalized['default_strategy'] || 'merge',
989
+ overrides: normalized[:overrides] || normalized['overrides'] || [],
990
+ replacements: normalized[:replacements] || normalized['replacements'] || {},
991
+ allowed_families: if normalized.key?(:allowed_families) || normalized.key?('allowed_families')
992
+ normalized[:allowed_families] || normalized['allowed_families']
993
+ end
788
994
  }
789
- return {
790
- request_kind: "profile",
791
- profile_name: normalized[:profile_name] || normalized["profile_name"],
792
- overrides: begin
793
- sparse = {
794
- mode: normalized[:mode] || normalized["mode"],
795
- template_root: normalized[:template_root] || normalized["template_root"],
796
- destination_root: normalized[:destination_root] || normalized["destination_root"]
797
- }
798
- context = normalized[:context] || normalized["context"]
799
- sparse[:context] = context if context.is_a?(Hash) && (context[:project_name] || context["project_name"])
800
- strategy = normalized[:default_strategy] || normalized["default_strategy"]
801
- sparse[:default_strategy] = strategy if strategy && strategy != "merge"
802
- overrides = normalized[:overrides] || normalized["overrides"]
803
- sparse[:overrides] = overrides if overrides.is_a?(Array) && !overrides.empty?
804
- replacements = normalized[:replacements] || normalized["replacements"]
805
- sparse[:replacements] = replacements if replacements.is_a?(Hash) && !replacements.empty?
806
- allowed_families = normalized.key?(:allowed_families) ? normalized[:allowed_families] : normalized["allowed_families"]
807
- sparse[:allowed_families] = allowed_families unless allowed_families.nil?
808
- sparse
809
- end
810
- } if request_kind == "profile"
995
+ if request_kind == 'profile'
996
+ return {
997
+ request_kind: 'profile',
998
+ profile_name: normalized[:profile_name] || normalized['profile_name'],
999
+ overrides: begin
1000
+ sparse = {
1001
+ mode: normalized[:mode] || normalized['mode'],
1002
+ template_root: normalized[:template_root] || normalized['template_root'],
1003
+ destination_root: normalized[:destination_root] || normalized['destination_root']
1004
+ }
1005
+ context = normalized[:context] || normalized['context']
1006
+ sparse[:context] = context if context.is_a?(Hash) && (context[:project_name] || context['project_name'])
1007
+ strategy = normalized[:default_strategy] || normalized['default_strategy']
1008
+ sparse[:default_strategy] = strategy if strategy && strategy != 'merge'
1009
+ overrides = normalized[:overrides] || normalized['overrides']
1010
+ sparse[:overrides] = overrides if overrides.is_a?(Array) && !overrides.empty?
1011
+ replacements = normalized[:replacements] || normalized['replacements']
1012
+ sparse[:replacements] = replacements if replacements.is_a?(Hash) && !replacements.empty?
1013
+ allowed_families = normalized.key?(:allowed_families) ? normalized[:allowed_families] : normalized['allowed_families']
1014
+ sparse[:allowed_families] = allowed_families unless allowed_families.nil?
1015
+ sparse
1016
+ end
1017
+ }
1018
+ end
811
1019
 
812
1020
  {
813
- request_kind: "options",
1021
+ request_kind: 'options',
814
1022
  options: options
815
1023
  }
816
1024
  end
817
1025
 
818
1026
  def report_template_directory_session_runner_payload(payload)
819
1027
  normalized = deep_dup(payload)
820
- request_kind = normalized[:request_kind] || normalized["request_kind"]
1028
+ request_kind = normalized[:request_kind] || normalized['request_kind']
821
1029
  request_kind = if request_kind.to_s.empty?
822
- (normalized.key?(:profile_name) || normalized.key?("profile_name") ||
823
- normalized.key?(:default_profile_name) || normalized.key?("default_profile_name")) ? "profile" : "options"
824
- else
825
- request_kind.to_s
826
- end
1030
+ if normalized.key?(:profile_name) || normalized.key?('profile_name') ||
1031
+ normalized.key?(:default_profile_name) || normalized.key?('default_profile_name')
1032
+ 'profile'
1033
+ else
1034
+ 'options'
1035
+ end
1036
+ else
1037
+ request_kind.to_s
1038
+ end
827
1039
 
828
1040
  result = {
829
1041
  request_kind: request_kind,
830
- mode: normalized[:mode] || normalized["mode"],
831
- template_root: normalized[:template_root] || normalized["template_root"],
832
- destination_root: normalized[:destination_root] || normalized["destination_root"],
833
- context: normalized[:context] || normalized["context"] || {},
834
- default_strategy: normalized[:default_strategy] || normalized["default_strategy"] || "merge",
835
- overrides: normalized[:overrides] || normalized["overrides"] || [],
836
- replacements: normalized[:replacements] || normalized["replacements"] || {},
837
- allowed_families: if normalized.key?(:allowed_families) || normalized.key?("allowed_families")
838
- normalized.key?(:allowed_families) ? normalized[:allowed_families] : normalized["allowed_families"]
839
- else
840
- nil
841
- end
1042
+ mode: normalized[:mode] || normalized['mode'],
1043
+ template_root: normalized[:template_root] || normalized['template_root'],
1044
+ destination_root: normalized[:destination_root] || normalized['destination_root'],
1045
+ context: normalized[:context] || normalized['context'] || {},
1046
+ default_strategy: normalized[:default_strategy] || normalized['default_strategy'] || 'merge',
1047
+ overrides: normalized[:overrides] || normalized['overrides'] || [],
1048
+ replacements: normalized[:replacements] || normalized['replacements'] || {},
1049
+ allowed_families: if normalized.key?(:allowed_families) || normalized.key?('allowed_families')
1050
+ normalized.key?(:allowed_families) ? normalized[:allowed_families] : normalized['allowed_families']
1051
+ end
842
1052
  }
843
- profile_name = normalized[:profile_name] || normalized["profile_name"] ||
844
- normalized[:default_profile_name] || normalized["default_profile_name"]
1053
+ profile_name = normalized[:profile_name] || normalized['profile_name'] ||
1054
+ normalized[:default_profile_name] || normalized['default_profile_name']
845
1055
  result[:profile_name] = profile_name if profile_name
846
1056
  result
847
1057
  end
@@ -857,60 +1067,72 @@ module Ast
857
1067
 
858
1068
  def template_directory_session_runner_payload_envelope(payload)
859
1069
  {
860
- kind: "template_directory_session_runner_payload",
1070
+ kind: 'template_directory_session_runner_payload',
861
1071
  version: SESSION_RUNNER_PAYLOAD_TRANSPORT_VERSION,
862
1072
  payload: deep_dup(payload)
863
1073
  }
864
1074
  end
865
1075
 
866
1076
  def import_template_directory_session_runner_payload_envelope(envelope)
867
- return [nil, { category: "kind_mismatch", message: "expected template_directory_session_runner_payload envelope kind." }] unless envelope[:kind] == "template_directory_session_runner_payload"
868
- return [nil, { category: "unsupported_version", message: "unsupported template_directory_session_runner_payload envelope version #{envelope[:version]}." }] unless envelope[:version] == SESSION_RUNNER_PAYLOAD_TRANSPORT_VERSION
1077
+ unless envelope[:kind] == 'template_directory_session_runner_payload'
1078
+ return [nil,
1079
+ { category: 'kind_mismatch',
1080
+ message: 'expected template_directory_session_runner_payload envelope kind.' }]
1081
+ end
1082
+ unless envelope[:version] == SESSION_RUNNER_PAYLOAD_TRANSPORT_VERSION
1083
+ return [nil,
1084
+ { category: 'unsupported_version',
1085
+ message: "unsupported template_directory_session_runner_payload envelope version #{envelope[:version]}." }]
1086
+ end
869
1087
 
870
1088
  [deep_dup(envelope[:payload]), nil]
871
1089
  end
872
1090
 
873
1091
  def run_template_directory_session_entrypoint(entrypoint, profiles = {})
874
1092
  normalized = deep_dup(entrypoint)
875
- return run_template_directory_session_runner_payload(
876
- normalized[:payload] || normalized["payload"],
877
- profiles
878
- ) if normalized[:payload] || normalized["payload"]
1093
+ if normalized[:payload] || normalized['payload']
1094
+ return run_template_directory_session_runner_payload(
1095
+ normalized[:payload] || normalized['payload'],
1096
+ profiles
1097
+ )
1098
+ end
879
1099
 
880
- return run_template_directory_session_runner_request(
881
- normalized[:request] || normalized["request"],
882
- profiles
883
- ) if normalized[:request] || normalized["request"]
1100
+ if normalized[:request] || normalized['request']
1101
+ return run_template_directory_session_runner_request(
1102
+ normalized[:request] || normalized['request'],
1103
+ profiles
1104
+ )
1105
+ end
884
1106
 
885
1107
  report_template_directory_session_configuration_outcome(
886
- "plan",
887
- { mode: "plan", ready: false, diagnostics: [] }
1108
+ 'plan',
1109
+ { mode: 'plan', ready: false, diagnostics: [] }
888
1110
  )
889
1111
  end
890
1112
 
891
1113
  def report_template_directory_session_entrypoint(entrypoint)
892
1114
  normalized = deep_dup(entrypoint)
893
- if normalized[:payload] || normalized["payload"]
1115
+ if normalized[:payload] || normalized['payload']
894
1116
  return {
895
- source_kind: "payload",
1117
+ source_kind: 'payload',
896
1118
  runner_request: report_template_directory_session_runner_input(
897
1119
  report_template_directory_session_runner_payload(
898
- normalized[:payload] || normalized["payload"]
1120
+ normalized[:payload] || normalized['payload']
899
1121
  )
900
1122
  )
901
1123
  }
902
1124
  end
903
1125
 
904
- if normalized[:request] || normalized["request"]
1126
+ if normalized[:request] || normalized['request']
905
1127
  return {
906
- source_kind: "request",
907
- runner_request: normalized[:request] || normalized["request"]
1128
+ source_kind: 'request',
1129
+ runner_request: normalized[:request] || normalized['request']
908
1130
  }
909
1131
  end
910
1132
 
911
1133
  {
912
- source_kind: "",
913
- runner_request: { request_kind: "options" }
1134
+ source_kind: '',
1135
+ runner_request: { request_kind: 'options' }
914
1136
  }
915
1137
  end
916
1138
 
@@ -1000,28 +1222,36 @@ module Ast
1000
1222
 
1001
1223
  def template_directory_session_inspection_envelope(inspection)
1002
1224
  {
1003
- kind: "template_directory_session_inspection",
1225
+ kind: 'template_directory_session_inspection',
1004
1226
  version: SESSION_INSPECTION_TRANSPORT_VERSION,
1005
1227
  inspection: deep_dup(inspection)
1006
1228
  }
1007
1229
  end
1008
1230
 
1009
1231
  def import_template_directory_session_inspection_envelope(envelope)
1010
- return [nil, { category: "kind_mismatch", message: "expected template_directory_session_inspection envelope kind." }] unless envelope[:kind] == "template_directory_session_inspection"
1011
- return [nil, { category: "unsupported_version", message: "unsupported template_directory_session_inspection envelope version #{envelope[:version]}." }] unless envelope[:version] == SESSION_INSPECTION_TRANSPORT_VERSION
1232
+ unless envelope[:kind] == 'template_directory_session_inspection'
1233
+ return [nil,
1234
+ { category: 'kind_mismatch',
1235
+ message: 'expected template_directory_session_inspection envelope kind.' }]
1236
+ end
1237
+ unless envelope[:version] == SESSION_INSPECTION_TRANSPORT_VERSION
1238
+ return [nil,
1239
+ { category: 'unsupported_version',
1240
+ message: "unsupported template_directory_session_inspection envelope version #{envelope[:version]}." }]
1241
+ end
1012
1242
 
1013
1243
  [deep_dup(envelope[:inspection]), nil]
1014
1244
  end
1015
1245
 
1016
1246
  def run_template_directory_session_dispatch(operation, entrypoint, profiles = {})
1017
1247
  case operation.to_s
1018
- when "inspect"
1248
+ when 'inspect'
1019
1249
  {
1020
1250
  operation: operation.to_s,
1021
1251
  inspection: report_template_directory_session_inspection(entrypoint, profiles),
1022
1252
  outcome: nil
1023
1253
  }
1024
- when "run"
1254
+ when 'run'
1025
1255
  {
1026
1256
  operation: operation.to_s,
1027
1257
  inspection: nil,
@@ -1035,10 +1265,10 @@ module Ast
1035
1265
  def run_template_directory_session_command(command, profiles = {})
1036
1266
  normalized = deep_dup(command)
1037
1267
  run_template_directory_session_dispatch(
1038
- normalized[:operation] || normalized["operation"],
1268
+ normalized[:operation] || normalized['operation'],
1039
1269
  {
1040
- payload: normalized[:payload] || normalized["payload"],
1041
- request: normalized[:request] || normalized["request"]
1270
+ payload: normalized[:payload] || normalized['payload'],
1271
+ request: normalized[:request] || normalized['request']
1042
1272
  },
1043
1273
  profiles
1044
1274
  )
@@ -1048,19 +1278,19 @@ module Ast
1048
1278
  normalized = deep_dup(command)
1049
1279
  run_template_directory_session_command(
1050
1280
  {
1051
- operation: normalized[:operation] || normalized["operation"],
1281
+ operation: normalized[:operation] || normalized['operation'],
1052
1282
  payload: {
1053
- request_kind: normalized[:request_kind] || normalized["request_kind"],
1054
- default_profile_name: normalized[:default_profile_name] || normalized["default_profile_name"],
1055
- profile_name: normalized[:profile_name] || normalized["profile_name"],
1056
- mode: normalized[:mode] || normalized["mode"],
1057
- template_root: normalized[:template_root] || normalized["template_root"],
1058
- destination_root: normalized[:destination_root] || normalized["destination_root"],
1059
- context: normalized[:context] || normalized["context"],
1060
- default_strategy: normalized[:default_strategy] || normalized["default_strategy"],
1061
- overrides: normalized[:overrides] || normalized["overrides"],
1062
- replacements: normalized[:replacements] || normalized["replacements"],
1063
- allowed_families: normalized.key?(:allowed_families) ? normalized[:allowed_families] : normalized["allowed_families"]
1283
+ request_kind: normalized[:request_kind] || normalized['request_kind'],
1284
+ default_profile_name: normalized[:default_profile_name] || normalized['default_profile_name'],
1285
+ profile_name: normalized[:profile_name] || normalized['profile_name'],
1286
+ mode: normalized[:mode] || normalized['mode'],
1287
+ template_root: normalized[:template_root] || normalized['template_root'],
1288
+ destination_root: normalized[:destination_root] || normalized['destination_root'],
1289
+ context: normalized[:context] || normalized['context'],
1290
+ default_strategy: normalized[:default_strategy] || normalized['default_strategy'],
1291
+ overrides: normalized[:overrides] || normalized['overrides'],
1292
+ replacements: normalized[:replacements] || normalized['replacements'],
1293
+ allowed_families: normalized.key?(:allowed_families) ? normalized[:allowed_families] : normalized['allowed_families']
1064
1294
  }
1065
1295
  },
1066
1296
  profiles
@@ -1069,7 +1299,7 @@ module Ast
1069
1299
 
1070
1300
  def run_template_directory_session(invocation, profiles = {})
1071
1301
  normalized = deep_dup(invocation)
1072
- if normalized[:payload] || normalized["payload"] || normalized[:request] || normalized["request"]
1302
+ if normalized[:payload] || normalized['payload'] || normalized[:request] || normalized['request']
1073
1303
  return run_template_directory_session_command(normalized, profiles)
1074
1304
  end
1075
1305
 
@@ -1078,76 +1308,108 @@ module Ast
1078
1308
 
1079
1309
  def template_directory_session_entrypoint_envelope(entrypoint)
1080
1310
  {
1081
- kind: "template_directory_session_entrypoint",
1311
+ kind: 'template_directory_session_entrypoint',
1082
1312
  version: SESSION_ENTRYPOINT_TRANSPORT_VERSION,
1083
1313
  entrypoint: deep_dup(entrypoint)
1084
1314
  }
1085
1315
  end
1086
1316
 
1087
1317
  def import_template_directory_session_entrypoint_envelope(envelope)
1088
- return [nil, { category: "kind_mismatch", message: "expected template_directory_session_entrypoint envelope kind." }] unless envelope[:kind] == "template_directory_session_entrypoint"
1089
- return [nil, { category: "unsupported_version", message: "unsupported template_directory_session_entrypoint envelope version #{envelope[:version]}." }] unless envelope[:version] == SESSION_ENTRYPOINT_TRANSPORT_VERSION
1318
+ unless envelope[:kind] == 'template_directory_session_entrypoint'
1319
+ return [nil,
1320
+ { category: 'kind_mismatch',
1321
+ message: 'expected template_directory_session_entrypoint envelope kind.' }]
1322
+ end
1323
+ unless envelope[:version] == SESSION_ENTRYPOINT_TRANSPORT_VERSION
1324
+ return [nil,
1325
+ { category: 'unsupported_version',
1326
+ message: "unsupported template_directory_session_entrypoint envelope version #{envelope[:version]}." }]
1327
+ end
1090
1328
 
1091
1329
  [deep_dup(envelope[:entrypoint]), nil]
1092
1330
  end
1093
1331
 
1094
1332
  def template_directory_session_command_envelope(command)
1095
1333
  {
1096
- kind: "template_directory_session_command",
1334
+ kind: 'template_directory_session_command',
1097
1335
  version: SESSION_COMMAND_TRANSPORT_VERSION,
1098
1336
  command: deep_dup(command)
1099
1337
  }
1100
1338
  end
1101
1339
 
1102
1340
  def import_template_directory_session_command_envelope(envelope)
1103
- return [nil, { category: "kind_mismatch", message: "expected template_directory_session_command envelope kind." }] unless envelope[:kind] == "template_directory_session_command"
1104
- return [nil, { category: "unsupported_version", message: "unsupported template_directory_session_command envelope version #{envelope[:version]}." }] unless envelope[:version] == SESSION_COMMAND_TRANSPORT_VERSION
1341
+ unless envelope[:kind] == 'template_directory_session_command'
1342
+ return [nil,
1343
+ { category: 'kind_mismatch',
1344
+ message: 'expected template_directory_session_command envelope kind.' }]
1345
+ end
1346
+ unless envelope[:version] == SESSION_COMMAND_TRANSPORT_VERSION
1347
+ return [nil,
1348
+ { category: 'unsupported_version',
1349
+ message: "unsupported template_directory_session_command envelope version #{envelope[:version]}." }]
1350
+ end
1105
1351
 
1106
1352
  [deep_dup(envelope[:command]), nil]
1107
1353
  end
1108
1354
 
1109
1355
  def template_directory_session_command_payload_envelope(payload)
1110
1356
  {
1111
- kind: "template_directory_session_command_payload",
1357
+ kind: 'template_directory_session_command_payload',
1112
1358
  version: SESSION_COMMAND_PAYLOAD_TRANSPORT_VERSION,
1113
1359
  payload: deep_dup(payload)
1114
1360
  }
1115
1361
  end
1116
1362
 
1117
1363
  def import_template_directory_session_command_payload_envelope(envelope)
1118
- return [nil, { category: "kind_mismatch", message: "expected template_directory_session_command_payload envelope kind." }] unless envelope[:kind] == "template_directory_session_command_payload"
1119
- return [nil, { category: "unsupported_version", message: "unsupported template_directory_session_command_payload envelope version #{envelope[:version]}." }] unless envelope[:version] == SESSION_COMMAND_PAYLOAD_TRANSPORT_VERSION
1364
+ unless envelope[:kind] == 'template_directory_session_command_payload'
1365
+ return [nil,
1366
+ { category: 'kind_mismatch',
1367
+ message: 'expected template_directory_session_command_payload envelope kind.' }]
1368
+ end
1369
+ unless envelope[:version] == SESSION_COMMAND_PAYLOAD_TRANSPORT_VERSION
1370
+ return [nil,
1371
+ { category: 'unsupported_version',
1372
+ message: "unsupported template_directory_session_command_payload envelope version #{envelope[:version]}." }]
1373
+ end
1120
1374
 
1121
1375
  [deep_dup(envelope[:payload]), nil]
1122
1376
  end
1123
1377
 
1124
1378
  def template_directory_session_invocation_envelope(invocation)
1125
1379
  {
1126
- kind: "template_directory_session_invocation",
1380
+ kind: 'template_directory_session_invocation',
1127
1381
  version: SESSION_INVOCATION_TRANSPORT_VERSION,
1128
1382
  invocation: deep_dup(invocation)
1129
1383
  }
1130
1384
  end
1131
1385
 
1132
1386
  def import_template_directory_session_invocation_envelope(envelope)
1133
- return [nil, { category: "kind_mismatch", message: "expected template_directory_session_invocation envelope kind." }] unless envelope[:kind] == "template_directory_session_invocation"
1134
- return [nil, { category: "unsupported_version", message: "unsupported template_directory_session_invocation envelope version #{envelope[:version]}." }] unless envelope[:version] == SESSION_INVOCATION_TRANSPORT_VERSION
1387
+ unless envelope[:kind] == 'template_directory_session_invocation'
1388
+ return [nil,
1389
+ { category: 'kind_mismatch',
1390
+ message: 'expected template_directory_session_invocation envelope kind.' }]
1391
+ end
1392
+ unless envelope[:version] == SESSION_INVOCATION_TRANSPORT_VERSION
1393
+ return [nil,
1394
+ { category: 'unsupported_version',
1395
+ message: "unsupported template_directory_session_invocation envelope version #{envelope[:version]}." }]
1396
+ end
1135
1397
 
1136
1398
  [deep_dup(envelope[:invocation]), nil]
1137
1399
  end
1138
1400
 
1139
1401
  def report_session_request_from_runner_request(request, profiles = {})
1140
1402
  normalized = deep_dup(request)
1141
- if (normalized[:request_kind] || normalized["request_kind"]).to_s == "profile"
1403
+ if (normalized[:request_kind] || normalized['request_kind']).to_s == 'profile'
1142
1404
  return report_template_directory_session_profile_request(
1143
1405
  profiles,
1144
- normalized[:profile_name] || normalized["profile_name"],
1145
- normalized[:overrides] || normalized["overrides"] || {}
1406
+ normalized[:profile_name] || normalized['profile_name'],
1407
+ normalized[:overrides] || normalized['overrides'] || {}
1146
1408
  )
1147
1409
  end
1148
1410
 
1149
1411
  report_template_directory_session_options_request(
1150
- normalized[:options] || normalized["options"] || {}
1412
+ normalized[:options] || normalized['options'] || {}
1151
1413
  )
1152
1414
  end
1153
1415
 
@@ -1158,22 +1420,22 @@ module Ast
1158
1420
  normalized_profile = deep_dup(profile)
1159
1421
  normalized_overrides = deep_dup(overrides)
1160
1422
  {
1161
- mode: normalized_overrides[:mode] || normalized_overrides["mode"] ||
1162
- normalized_profile[:mode] || normalized_profile["mode"],
1163
- template_root: normalized_overrides[:template_root] || normalized_overrides["template_root"],
1164
- destination_root: normalized_overrides[:destination_root] || normalized_overrides["destination_root"],
1165
- context: normalized_overrides[:context] || normalized_overrides["context"] ||
1166
- normalized_profile[:context] || normalized_profile["context"] || {},
1167
- default_strategy: normalized_overrides[:default_strategy] || normalized_overrides["default_strategy"] ||
1168
- normalized_profile[:default_strategy] || normalized_profile["default_strategy"],
1169
- overrides: normalized_overrides[:overrides] || normalized_overrides["overrides"] ||
1170
- normalized_profile[:overrides] || normalized_profile["overrides"] || [],
1171
- replacements: normalized_overrides[:replacements] || normalized_overrides["replacements"] ||
1172
- normalized_profile[:replacements] || normalized_profile["replacements"] || {},
1173
- allowed_families: normalized_overrides[:allowed_families] || normalized_overrides["allowed_families"] ||
1174
- normalized_profile[:allowed_families] || normalized_profile["allowed_families"],
1175
- config: normalized_overrides[:config] || normalized_overrides["config"] ||
1176
- normalized_profile[:config] || normalized_profile["config"]
1423
+ mode: normalized_overrides[:mode] || normalized_overrides['mode'] ||
1424
+ normalized_profile[:mode] || normalized_profile['mode'],
1425
+ template_root: normalized_overrides[:template_root] || normalized_overrides['template_root'],
1426
+ destination_root: normalized_overrides[:destination_root] || normalized_overrides['destination_root'],
1427
+ context: normalized_overrides[:context] || normalized_overrides['context'] ||
1428
+ normalized_profile[:context] || normalized_profile['context'] || {},
1429
+ default_strategy: normalized_overrides[:default_strategy] || normalized_overrides['default_strategy'] ||
1430
+ normalized_profile[:default_strategy] || normalized_profile['default_strategy'],
1431
+ overrides: normalized_overrides[:overrides] || normalized_overrides['overrides'] ||
1432
+ normalized_profile[:overrides] || normalized_profile['overrides'] || [],
1433
+ replacements: normalized_overrides[:replacements] || normalized_overrides['replacements'] ||
1434
+ normalized_profile[:replacements] || normalized_profile['replacements'] || {},
1435
+ allowed_families: normalized_overrides[:allowed_families] || normalized_overrides['allowed_families'] ||
1436
+ normalized_profile[:allowed_families] || normalized_profile['allowed_families'],
1437
+ config: normalized_overrides[:config] || normalized_overrides['config'] ||
1438
+ normalized_profile[:config] || normalized_profile['config']
1177
1439
  }
1178
1440
  end
1179
1441
 
@@ -1191,19 +1453,71 @@ module Ast
1191
1453
 
1192
1454
  private
1193
1455
 
1456
+ def replace_or_insert_markdown_heading_section(content, heading_text, heading_level, replacement)
1457
+ normalized = content.to_s.rstrip
1458
+ return "#{replacement}\n" if normalized.empty?
1459
+
1460
+ lines = normalized.split("\n")
1461
+ heading = "#{'#' * heading_level} #{heading_text}"
1462
+ heading_index = lines.index { |line| line.strip == heading }
1463
+ if heading_index
1464
+ section_end = lines.length
1465
+ ((heading_index + 1)...lines.length).each do |index|
1466
+ level = markdown_heading_level(lines[index])
1467
+ if level.positive? && level <= heading_level
1468
+ section_end = index
1469
+ break
1470
+ end
1471
+ end
1472
+ output_lines = lines[0...heading_index] + replacement.split("\n")
1473
+ output_lines << '' if section_end < lines.length && lines[section_end] != ''
1474
+ output_lines += lines[section_end..]
1475
+ return "#{output_lines.join("\n")}\n"
1476
+ end
1477
+
1478
+ insert_at = readme_family_section_insert_index(lines)
1479
+ output_lines = lines[0...insert_at]
1480
+ output_lines << '' if output_lines.any? && output_lines.last != ''
1481
+ output_lines += replacement.split("\n")
1482
+ output_lines << '' if insert_at < lines.length && lines[insert_at] != ''
1483
+ output_lines += lines[insert_at..]
1484
+ "#{output_lines.join("\n")}\n"
1485
+ end
1486
+
1487
+ def markdown_heading_level(line)
1488
+ match = line.to_s.strip.match(/\A(#+)\s/)
1489
+ match && match[1].length <= 6 ? match[1].length : 0
1490
+ end
1491
+
1492
+ def readme_family_section_insert_index(lines)
1493
+ index = 0
1494
+ if markdown_heading_level(lines[0]) == 1
1495
+ index = 1
1496
+ index += 1 while index < lines.length && lines[index] == ''
1497
+ end
1498
+ while index < lines.length
1499
+ if lines[index] == ''
1500
+ index += 1 while index < lines.length && lines[index] == ''
1501
+ return index
1502
+ end
1503
+ index += 1
1504
+ end
1505
+ lines.length
1506
+ end
1507
+
1194
1508
  def deep_dup(value)
1195
1509
  Ast::Merge.deep_dup(value)
1196
1510
  end
1197
1511
 
1198
1512
  def normalize_session_mode(mode)
1199
1513
  normalized_mode = mode.to_s
1200
- MODES.include?(normalized_mode) ? normalized_mode : "plan"
1514
+ MODES.include?(normalized_mode) ? normalized_mode : 'plan'
1201
1515
  end
1202
1516
 
1203
1517
  def compact_session_request_options(options)
1204
1518
  normalized = deep_dup(options)
1205
1519
  normalized.delete(:config)
1206
- normalized.delete("config")
1520
+ normalized.delete('config')
1207
1521
  normalized
1208
1522
  end
1209
1523
  end