kettle-dev 1.1.59 → 1.2.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +1 -2
- data/.envrc +1 -0
- data/.envrc.example +1 -0
- data/.envrc.no-osc.example +1 -0
- data/.github/workflows/ancient.yml +1 -1
- data/.github/workflows/ancient.yml.example +1 -1
- data/.github/workflows/codeql-analysis.yml +1 -1
- data/.github/workflows/coverage.yml +1 -1
- data/.github/workflows/coverage.yml.example +1 -1
- data/.github/workflows/current.yml +1 -1
- data/.github/workflows/current.yml.example +1 -1
- data/.github/workflows/dep-heads.yml +1 -1
- data/.github/workflows/dependency-review.yml +1 -1
- data/.github/workflows/heads.yml +1 -1
- data/.github/workflows/heads.yml.example +1 -1
- data/.github/workflows/jruby.yml +1 -1
- data/.github/workflows/jruby.yml.example +1 -1
- data/.github/workflows/legacy.yml +1 -1
- data/.github/workflows/license-eye.yml +1 -1
- data/.github/workflows/locked_deps.yml +1 -1
- data/.github/workflows/opencollective.yml +1 -1
- data/.github/workflows/style.yml +1 -1
- data/.github/workflows/supported.yml +1 -1
- data/.github/workflows/truffle.yml +1 -1
- data/.github/workflows/unlocked_deps.yml +1 -1
- data/.github/workflows/unsupported.yml +1 -1
- data/CHANGELOG.md +35 -1
- data/Gemfile +3 -0
- data/Gemfile.example +3 -0
- data/README.md +90 -37
- data/README.md.example +16 -12
- data/README.md.no-osc.example +16 -12
- data/Rakefile.example +1 -1
- data/gemfiles/modular/style.gemfile.example +1 -1
- data/gemfiles/modular/templating.gemfile +3 -0
- data/lib/kettle/dev/appraisals_ast_merger.rb +383 -0
- data/lib/kettle/dev/changelog_cli.rb +13 -0
- data/lib/kettle/dev/modular_gemfiles.rb +11 -3
- data/lib/kettle/dev/prism_utils.rb +188 -0
- data/lib/kettle/dev/rakelib/spec_test.rake +70 -20
- data/lib/kettle/dev/source_merger.rb +345 -0
- data/lib/kettle/dev/tasks/template_task.rb +11 -1
- data/lib/kettle/dev/template_helpers.rb +70 -226
- data/lib/kettle/dev/version.rb +1 -1
- data/lib/kettle/dev.rb +2 -0
- data/sig/kettle/dev/appraisals_ast_merger.rbs +72 -0
- data/sig/kettle/dev/changelog_cli.rbs +64 -0
- data/sig/kettle/dev/prism_utils.rbs +56 -0
- data/sig/kettle/dev/source_merger.rbs +86 -0
- data/sig/kettle/dev/versioning.rbs +21 -0
- data.tar.gz.sig +0 -0
- metadata +14 -5
- metadata.gz.sig +0 -0
- /data/sig/kettle/dev/{dvcscli.rbs → dvcs_cli.rbs} +0 -0
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
# External stdlibs
|
|
4
4
|
require "find"
|
|
5
5
|
require "set"
|
|
6
|
+
require "yaml"
|
|
6
7
|
|
|
7
8
|
module Kettle
|
|
8
9
|
module Dev
|
|
9
10
|
# Helpers shared by kettle:dev Rake tasks for templating and file ops.
|
|
10
11
|
module TemplateHelpers
|
|
12
|
+
require "kettle/dev/appraisals_ast_merger"
|
|
13
|
+
|
|
11
14
|
# Track results of templating actions across a single process run.
|
|
12
15
|
# Keys: absolute destination paths (String)
|
|
13
16
|
# Values: Hash with keys: :action (Symbol, one of :create, :replace, :skip, :dir_create, :dir_replace), :timestamp (Time)
|
|
@@ -17,6 +20,12 @@ module Kettle
|
|
|
17
20
|
# The minimum Ruby supported by setup-ruby GHA
|
|
18
21
|
MIN_SETUP_RUBY = Gem::Version.create("2.3")
|
|
19
22
|
|
|
23
|
+
TEMPLATE_MANIFEST_PATH = File.expand_path("../../..", __dir__) + "/template_manifest.yml"
|
|
24
|
+
RUBY_BASENAMES = %w[Gemfile Rakefile Appraisals Appraisal.root.gemfile].freeze
|
|
25
|
+
RUBY_SUFFIXES = %w[.gemspec .gemfile].freeze
|
|
26
|
+
RUBY_EXTENSIONS = %w[.rb .rake].freeze
|
|
27
|
+
@@manifestation = nil
|
|
28
|
+
|
|
20
29
|
module_function
|
|
21
30
|
|
|
22
31
|
# Root of the host project where Rake was invoked
|
|
@@ -279,43 +288,15 @@ module Kettle
|
|
|
279
288
|
# If replacement fails unexpectedly, proceed with content as-is
|
|
280
289
|
end
|
|
281
290
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
""
|
|
291
|
-
end
|
|
292
|
-
content = merge_appraisals(content, existing)
|
|
293
|
-
end
|
|
294
|
-
rescue StandardError => e
|
|
295
|
-
Kettle::Dev.debug_error(e, __method__)
|
|
296
|
-
# On any error, fall back to generated content
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
# If updating a Gemfile or modular .gemfile and the destination already exists,
|
|
300
|
-
# merge dependency lines from the source into the destination to preserve any
|
|
301
|
-
# user-defined gem entries. We append missing `gem "name"` lines; we never
|
|
302
|
-
# alter or remove existing gem lines in the destination.
|
|
303
|
-
begin
|
|
304
|
-
if dest_exists
|
|
305
|
-
dest_str = dest_path.to_s
|
|
306
|
-
is_gemfile_like = File.basename(dest_str) == "Gemfile" || dest_str.end_with?(".gemfile")
|
|
307
|
-
if is_gemfile_like && File.file?(dest_str)
|
|
308
|
-
begin
|
|
309
|
-
existing = File.read(dest_str)
|
|
310
|
-
content = merge_gemfile_dependencies(content, existing)
|
|
311
|
-
rescue StandardError => e
|
|
312
|
-
Kettle::Dev.debug_error(e, __method__)
|
|
313
|
-
# If merging fails, fall back to writing generated content
|
|
314
|
-
end
|
|
315
|
-
end
|
|
291
|
+
basename = File.basename(dest_path.to_s)
|
|
292
|
+
content = apply_appraisals_merge(content, dest_path) if basename == "Appraisals"
|
|
293
|
+
if basename == "Appraisal.root.gemfile" && File.exist?(dest_path)
|
|
294
|
+
begin
|
|
295
|
+
prior = File.read(dest_path)
|
|
296
|
+
content = merge_gemfile_dependencies(content, prior)
|
|
297
|
+
rescue StandardError => e
|
|
298
|
+
Kettle::Dev.debug_error(e, __method__)
|
|
316
299
|
end
|
|
317
|
-
rescue StandardError => e
|
|
318
|
-
Kettle::Dev.debug_error(e, __method__)
|
|
319
300
|
end
|
|
320
301
|
|
|
321
302
|
write_file(dest_path, content)
|
|
@@ -456,197 +437,17 @@ module Kettle
|
|
|
456
437
|
end
|
|
457
438
|
end
|
|
458
439
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
# if any; otherwise retain destination's header comments.
|
|
466
|
-
# * If destination lacks the block, add the full template block (with its header).
|
|
467
|
-
# - Preserve destination-only blocks (not present in template) unchanged and after
|
|
468
|
-
# the merged template-ordered blocks.
|
|
469
|
-
# - Preamble (content before first appraise) comes from template when present, else destination.
|
|
470
|
-
def merge_appraisals(template_content, dest_content)
|
|
471
|
-
begin
|
|
472
|
-
# Helper: extract contiguous leading header lines (comments and blank lines)
|
|
473
|
-
extract_leading_header = lambda do |text|
|
|
474
|
-
lines = text.lines
|
|
475
|
-
header_lines = []
|
|
476
|
-
idx = 0
|
|
477
|
-
while idx < lines.length
|
|
478
|
-
ln = lines[idx]
|
|
479
|
-
break unless ln.strip.empty? || ln.lstrip.start_with?("#")
|
|
480
|
-
header_lines << ln
|
|
481
|
-
idx += 1
|
|
482
|
-
end
|
|
483
|
-
body = (idx < lines.length) ? lines[idx..-1].join : ""
|
|
484
|
-
{header: header_lines.join, body: body}
|
|
485
|
-
end
|
|
486
|
-
|
|
487
|
-
parse_blocks = lambda do |text|
|
|
488
|
-
lines = text.lines
|
|
489
|
-
blocks = []
|
|
490
|
-
i = 0
|
|
491
|
-
while i < lines.length
|
|
492
|
-
line = lines[i]
|
|
493
|
-
if line =~ /^\s*appraise\s+["']([^"']+)["']\s+do\s*$/
|
|
494
|
-
name = $1
|
|
495
|
-
# collect header comment lines immediately preceding (contiguous, no blank between comment group and appraise line)
|
|
496
|
-
header_lines = []
|
|
497
|
-
j = i - 1
|
|
498
|
-
while j >= 0
|
|
499
|
-
prev = lines[j]
|
|
500
|
-
break if prev.strip.empty?
|
|
501
|
-
if prev.lstrip.start_with?("#")
|
|
502
|
-
header_lines.unshift(prev)
|
|
503
|
-
j -= 1
|
|
504
|
-
else
|
|
505
|
-
break
|
|
506
|
-
end
|
|
507
|
-
end
|
|
508
|
-
body_lines = []
|
|
509
|
-
i += 1
|
|
510
|
-
while i < lines.length
|
|
511
|
-
l2 = lines[i]
|
|
512
|
-
if l2 =~ /^\s*end\s*$/
|
|
513
|
-
end_line = l2
|
|
514
|
-
blocks << {
|
|
515
|
-
name: name,
|
|
516
|
-
header: header_lines.dup,
|
|
517
|
-
body: body_lines.dup,
|
|
518
|
-
end_line: end_line,
|
|
519
|
-
raw_order: blocks.length,
|
|
520
|
-
original_indices: (j ? (j + 1)..i : i),
|
|
521
|
-
}
|
|
522
|
-
break
|
|
523
|
-
else
|
|
524
|
-
body_lines << l2
|
|
525
|
-
end
|
|
526
|
-
i += 1
|
|
527
|
-
end
|
|
528
|
-
end
|
|
529
|
-
i += 1
|
|
530
|
-
end
|
|
531
|
-
preamble = if blocks.empty?
|
|
532
|
-
text
|
|
533
|
-
else
|
|
534
|
-
# preamble = lines from start up to first block start (exclusive)
|
|
535
|
-
first_block = blocks.first
|
|
536
|
-
# Take lines up to first occurrence of the appraise line (supports either quote type)
|
|
537
|
-
re = /^\s*appraise\s+["']#{Regexp.escape(first_block[:name])}["']\s+do\s*$/
|
|
538
|
-
idx = lines.index { |l| l =~ re } || 0
|
|
539
|
-
lines[0...idx].join
|
|
540
|
-
end
|
|
541
|
-
{blocks: blocks, preamble: preamble}
|
|
542
|
-
end
|
|
543
|
-
|
|
544
|
-
# Extract leading headers from template and destination and parse their bodies
|
|
545
|
-
tmpl_parts = extract_leading_header.call(template_content)
|
|
546
|
-
dest_parts = extract_leading_header.call(dest_content)
|
|
547
|
-
|
|
548
|
-
# If the template does not provide a leading header, preserve the destination as-is
|
|
549
|
-
# so that per-block adjacent header comments (including those at the top of file)
|
|
550
|
-
# are still parsed and preserved. Only strip the destination leading header when
|
|
551
|
-
# the template has a leading header to replace it.
|
|
552
|
-
dest_parse_source = if tmpl_parts[:header].to_s.strip.empty?
|
|
553
|
-
dest_content
|
|
554
|
-
else
|
|
555
|
-
dest_parts[:body]
|
|
556
|
-
end
|
|
557
|
-
|
|
558
|
-
tmpl = parse_blocks.call(tmpl_parts[:body])
|
|
559
|
-
dest = parse_blocks.call(dest_parse_source)
|
|
560
|
-
tmpl_blocks = tmpl[:blocks]
|
|
561
|
-
dest_blocks = dest[:blocks]
|
|
562
|
-
dest_by_name = dest_blocks.map { |b| [b[:name], b] }.to_h
|
|
563
|
-
|
|
564
|
-
merged_blocks_strings = []
|
|
565
|
-
gem_or_eval_re = /^\s*(?:gem|eval_gemfile)\b/
|
|
566
|
-
|
|
567
|
-
tmpl_blocks.each do |tb|
|
|
568
|
-
if (db = dest_by_name[tb[:name]])
|
|
569
|
-
# Merge lines
|
|
570
|
-
existing_lines = db[:body].map(&:rstrip)
|
|
571
|
-
existing_set = existing_lines.to_set
|
|
572
|
-
# Collect template gem/eval lines
|
|
573
|
-
tmpl_needed = tb[:body].select { |l| gem_or_eval_re =~ l }
|
|
574
|
-
additions = []
|
|
575
|
-
tmpl_needed.each do |l|
|
|
576
|
-
line_key = l.rstrip
|
|
577
|
-
additions << l unless existing_set.include?(line_key)
|
|
578
|
-
end
|
|
579
|
-
merged_body = db[:body].dup
|
|
580
|
-
unless additions.empty?
|
|
581
|
-
# insert before end (just append; body excludes 'end')
|
|
582
|
-
merged_body += additions
|
|
583
|
-
end
|
|
584
|
-
header = tb[:header].any? ? tb[:header] : db[:header]
|
|
585
|
-
# If the template provides no leading header and the destination preamble
|
|
586
|
-
# already ends with this header, skip emitting the header for this block
|
|
587
|
-
# to avoid duplicating it (it will already be present at the top of the file).
|
|
588
|
-
header_to_emit = if tmpl_parts[:header].to_s.strip.empty? && !dest[:preamble].to_s.strip.empty? && !header.empty? && dest[:preamble].to_s.end_with?(header.join)
|
|
589
|
-
[]
|
|
590
|
-
else
|
|
591
|
-
header
|
|
592
|
-
end
|
|
593
|
-
block_text = +""
|
|
594
|
-
block_text << "\n" unless merged_blocks_strings.empty?
|
|
595
|
-
header_to_emit.each { |hl| block_text << hl } if header_to_emit.any?
|
|
596
|
-
block_text << "appraise \"#{tb[:name]}\" do\n"
|
|
597
|
-
merged_body.each { |bl| block_text << bl }
|
|
598
|
-
block_text << db[:end_line]
|
|
599
|
-
merged_blocks_strings << block_text
|
|
600
|
-
dest_by_name.delete(tb[:name])
|
|
601
|
-
else
|
|
602
|
-
# New block from template
|
|
603
|
-
block_text = +""
|
|
604
|
-
block_text << "\n" unless merged_blocks_strings.empty?
|
|
605
|
-
tb[:header].each { |hl| block_text << hl } if tb[:header].any?
|
|
606
|
-
block_text << "appraise \"#{tb[:name]}\" do\n"
|
|
607
|
-
tb[:body].each { |bl| block_text << bl }
|
|
608
|
-
block_text << tb[:end_line]
|
|
609
|
-
merged_blocks_strings << block_text
|
|
610
|
-
end
|
|
611
|
-
end
|
|
612
|
-
# Append destination-only blocks preserving their original text
|
|
613
|
-
dest_remaining_order = dest_blocks.select { |b| dest_by_name.key?(b[:name]) }
|
|
614
|
-
dest_remaining_order.each do |b|
|
|
615
|
-
block_text = +""
|
|
616
|
-
block_text << "\n" unless merged_blocks_strings.empty?
|
|
617
|
-
b[:header].each { |hl| block_text << hl } if b[:header].any?
|
|
618
|
-
block_text << "appraise \"#{b[:name]}\" do\n"
|
|
619
|
-
b[:body].each { |bl| block_text << bl }
|
|
620
|
-
block_text << b[:end_line]
|
|
621
|
-
merged_blocks_strings << block_text
|
|
622
|
-
end
|
|
623
|
-
|
|
624
|
-
# Build final preamble:
|
|
625
|
-
# - If template provides a leading header, use that header and then prefer the
|
|
626
|
-
# template's body preamble when present, otherwise fall back to the
|
|
627
|
-
# destination's body preamble.
|
|
628
|
-
# - If template does NOT provide a leading header, leave the destination
|
|
629
|
-
# preamble (including any leading header/comments) intact.
|
|
630
|
-
preamble = +""
|
|
631
|
-
if tmpl_parts[:header].to_s.strip.empty?
|
|
632
|
-
preamble << dest[:preamble].to_s
|
|
633
|
-
else
|
|
634
|
-
body_preamble = tmpl[:preamble].to_s.strip.empty? ? dest[:preamble].to_s : tmpl[:preamble].to_s
|
|
635
|
-
preamble << tmpl_parts[:header].to_s
|
|
636
|
-
preamble << body_preamble.to_s
|
|
637
|
-
end
|
|
638
|
-
|
|
639
|
-
out = +""
|
|
640
|
-
out << preamble unless preamble.nil? || preamble.empty?
|
|
641
|
-
out << "\n" unless out.end_with?("\n")
|
|
642
|
-
out << merged_blocks_strings.join
|
|
643
|
-
out << "\n" unless out.end_with?("\n")
|
|
644
|
-
out
|
|
645
|
-
rescue StandardError => e
|
|
646
|
-
Kettle::Dev.debug_error(e, __method__)
|
|
647
|
-
# Fallback: prefer destination (user changes) and append template content to allow manual reconciliation
|
|
648
|
-
dest_content + "\n# --- TEMPLATE APPRAISALS (unmerged) ---\n" + template_content
|
|
440
|
+
def apply_appraisals_merge(content, dest_path)
|
|
441
|
+
dest = dest_path.to_s
|
|
442
|
+
existing = if File.exist?(dest)
|
|
443
|
+
File.read(dest)
|
|
444
|
+
else
|
|
445
|
+
""
|
|
649
446
|
end
|
|
447
|
+
Kettle::Dev::AppraisalsAstMerger.merge(content, existing)
|
|
448
|
+
rescue StandardError => e
|
|
449
|
+
Kettle::Dev.debug_error(e, __method__)
|
|
450
|
+
content
|
|
650
451
|
end
|
|
651
452
|
|
|
652
453
|
# Copy a directory tree, prompting before creating or overwriting.
|
|
@@ -899,6 +700,49 @@ module Kettle
|
|
|
899
700
|
def gemspec_metadata(root = project_root)
|
|
900
701
|
Kettle::Dev::GemSpecReader.load(root)
|
|
901
702
|
end
|
|
703
|
+
|
|
704
|
+
def apply_strategy(content, dest_path)
|
|
705
|
+
return content unless ruby_template?(dest_path)
|
|
706
|
+
strategy = strategy_for(dest_path)
|
|
707
|
+
dest_content = File.exist?(dest_path) ? File.read(dest_path) : ""
|
|
708
|
+
Kettle::Dev::SourceMerger.apply(strategy: strategy, src: content, dest: dest_content, path: rel_path(dest_path))
|
|
709
|
+
end
|
|
710
|
+
|
|
711
|
+
def manifestation
|
|
712
|
+
@@manifestation ||= load_manifest
|
|
713
|
+
end
|
|
714
|
+
|
|
715
|
+
def strategy_for(dest_path)
|
|
716
|
+
relative = rel_path(dest_path)
|
|
717
|
+
manifestation.find do |entry|
|
|
718
|
+
File.fnmatch?(entry[:path], relative, File::FNM_PATHNAME | File::FNM_EXTGLOB | File::FNM_DOTMATCH)
|
|
719
|
+
end&.fetch(:strategy, :skip) || :skip
|
|
720
|
+
end
|
|
721
|
+
|
|
722
|
+
def rel_path(path)
|
|
723
|
+
project = project_root.to_s
|
|
724
|
+
path.to_s.sub(/^#{Regexp.escape(project)}\/?/, "")
|
|
725
|
+
end
|
|
726
|
+
|
|
727
|
+
def ruby_template?(dest_path)
|
|
728
|
+
base = File.basename(dest_path.to_s)
|
|
729
|
+
return true if RUBY_BASENAMES.include?(base)
|
|
730
|
+
return true if RUBY_SUFFIXES.any? { |suffix| base.end_with?(suffix) }
|
|
731
|
+
ext = File.extname(base)
|
|
732
|
+
RUBY_EXTENSIONS.include?(ext)
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
def load_manifest
|
|
736
|
+
raw = YAML.load_file(TEMPLATE_MANIFEST_PATH)
|
|
737
|
+
raw.map do |entry|
|
|
738
|
+
{
|
|
739
|
+
path: entry["path"],
|
|
740
|
+
strategy: entry["strategy"].to_s.strip.downcase.to_sym,
|
|
741
|
+
}
|
|
742
|
+
end
|
|
743
|
+
rescue Errno::ENOENT
|
|
744
|
+
[]
|
|
745
|
+
end
|
|
902
746
|
end
|
|
903
747
|
end
|
|
904
748
|
end
|
data/lib/kettle/dev/version.rb
CHANGED
data/lib/kettle/dev.rb
CHANGED
|
@@ -21,8 +21,10 @@ module Kettle
|
|
|
21
21
|
autoload :GitAdapter, "kettle/dev/git_adapter"
|
|
22
22
|
autoload :GitCommitFooter, "kettle/dev/git_commit_footer"
|
|
23
23
|
autoload :InputAdapter, "kettle/dev/input_adapter"
|
|
24
|
+
autoload :PrismUtils, "kettle/dev/prism_utils"
|
|
24
25
|
autoload :ReadmeBackers, "kettle/dev/readme_backers"
|
|
25
26
|
autoload :OpenCollectiveConfig, "kettle/dev/open_collective_config"
|
|
27
|
+
autoload :SourceMerger, "kettle/dev/source_merger"
|
|
26
28
|
autoload :ReleaseCLI, "kettle/dev/release_cli"
|
|
27
29
|
autoload :PreReleaseCLI, "kettle/dev/pre_release_cli"
|
|
28
30
|
autoload :SetupCLI, "kettle/dev/setup_cli"
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# TypeProf 0.21.11
|
|
2
|
+
|
|
3
|
+
module Kettle
|
|
4
|
+
module Dev
|
|
5
|
+
# AST-driven merger for Appraisals files using Prism
|
|
6
|
+
module AppraisalsAstMerger
|
|
7
|
+
TRACKED_METHODS: Array[Symbol]
|
|
8
|
+
|
|
9
|
+
# Merge template and destination Appraisals files preserving comments
|
|
10
|
+
def self.merge: (String template_content, String dest_content) -> String
|
|
11
|
+
|
|
12
|
+
# Extract blocks and preamble from parse result
|
|
13
|
+
def self.extract_blocks: (
|
|
14
|
+
Prism::ParseResult parse_result,
|
|
15
|
+
String source_content
|
|
16
|
+
) -> [Array[Prism::Comment], Array[Hash[Symbol, untyped]]]
|
|
17
|
+
|
|
18
|
+
# Check if node is an appraise call
|
|
19
|
+
def self.appraise_call?: (Prism::Node node) -> bool
|
|
20
|
+
|
|
21
|
+
# Extract appraise block name from node
|
|
22
|
+
def self.extract_appraise_name: (Prism::Node? node) -> String?
|
|
23
|
+
|
|
24
|
+
# Merge preamble comments from template and destination
|
|
25
|
+
def self.merge_preambles: (
|
|
26
|
+
Array[Prism::Comment] tmpl_comments,
|
|
27
|
+
Array[Prism::Comment] dest_comments
|
|
28
|
+
) -> Array[String]
|
|
29
|
+
|
|
30
|
+
# Extract block header comments
|
|
31
|
+
def self.extract_block_header: (
|
|
32
|
+
Prism::Node node,
|
|
33
|
+
Array[String] source_lines,
|
|
34
|
+
Array[Hash[Symbol, untyped]] previous_blocks
|
|
35
|
+
) -> String
|
|
36
|
+
|
|
37
|
+
# Merge blocks from template and destination
|
|
38
|
+
def self.merge_blocks: (
|
|
39
|
+
Array[Hash[Symbol, untyped]] tmpl_blocks,
|
|
40
|
+
Array[Hash[Symbol, untyped]] dest_blocks,
|
|
41
|
+
Prism::ParseResult tmpl_result,
|
|
42
|
+
Prism::ParseResult dest_result
|
|
43
|
+
) -> Array[Hash[Symbol, untyped]]
|
|
44
|
+
|
|
45
|
+
# Merge statements within a block
|
|
46
|
+
def self.merge_block_statements: (
|
|
47
|
+
Prism::Node? tmpl_body,
|
|
48
|
+
Prism::Node? dest_body,
|
|
49
|
+
Prism::ParseResult dest_result
|
|
50
|
+
) -> Array[Hash[Symbol, untyped]]
|
|
51
|
+
|
|
52
|
+
# Generate statement key for deduplication
|
|
53
|
+
def self.statement_key: (Prism::Node node) -> [Symbol, String?]?
|
|
54
|
+
|
|
55
|
+
# Build output from preamble and blocks
|
|
56
|
+
def self.build_output: (
|
|
57
|
+
Array[String] preamble_lines,
|
|
58
|
+
Array[Hash[Symbol, untyped]] blocks
|
|
59
|
+
) -> String
|
|
60
|
+
|
|
61
|
+
# Normalize statement to use parentheses
|
|
62
|
+
def self.normalize_statement: (Prism::Node node) -> String
|
|
63
|
+
|
|
64
|
+
# Normalize argument to canonical format
|
|
65
|
+
def self.normalize_argument: (Prism::Node arg) -> String
|
|
66
|
+
|
|
67
|
+
# Extract original statements from node
|
|
68
|
+
def self.extract_original_statements: (Prism::Node node) -> Array[Hash[Symbol, untyped]]
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# TypeProf 0.21.11
|
|
2
|
+
|
|
3
|
+
module Kettle
|
|
4
|
+
module Dev
|
|
5
|
+
# CLI for updating CHANGELOG.md with new version sections
|
|
6
|
+
class ChangelogCLI
|
|
7
|
+
UNRELEASED_SECTION_HEADING: String
|
|
8
|
+
|
|
9
|
+
@root: String
|
|
10
|
+
@changelog_path: String
|
|
11
|
+
@coverage_path: String
|
|
12
|
+
|
|
13
|
+
def initialize: () -> void
|
|
14
|
+
|
|
15
|
+
# Main entry point that updates CHANGELOG.md
|
|
16
|
+
def run: () -> void
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
# Abort with error message
|
|
21
|
+
def abort: (String msg) -> void
|
|
22
|
+
|
|
23
|
+
# Detect version from lib/**/version.rb
|
|
24
|
+
def detect_version: () -> String
|
|
25
|
+
|
|
26
|
+
# Extract unreleased section from changelog
|
|
27
|
+
def extract_unreleased: (String content) -> [String?, String?, String?]
|
|
28
|
+
|
|
29
|
+
# Detect previous version from after text
|
|
30
|
+
def detect_previous_version: (String after_text) -> String?
|
|
31
|
+
|
|
32
|
+
# Filter unreleased sections keeping only those with content
|
|
33
|
+
def filter_unreleased_sections: (String unreleased_block) -> String
|
|
34
|
+
|
|
35
|
+
# Get coverage lines from coverage.json
|
|
36
|
+
def coverage_lines: () -> [String?, String?]
|
|
37
|
+
|
|
38
|
+
# Get YARD documentation percentage
|
|
39
|
+
def yard_percent_documented: () -> String?
|
|
40
|
+
|
|
41
|
+
# Convert legacy heading tag suffix to list format
|
|
42
|
+
def convert_heading_tag_suffix_to_list: (String text) -> String
|
|
43
|
+
|
|
44
|
+
# Update link references in changelog
|
|
45
|
+
def update_link_refs: (
|
|
46
|
+
String content,
|
|
47
|
+
String? owner,
|
|
48
|
+
String? repo,
|
|
49
|
+
String? prev_version,
|
|
50
|
+
String new_version
|
|
51
|
+
) -> String
|
|
52
|
+
|
|
53
|
+
# Normalize spacing around headings
|
|
54
|
+
def normalize_heading_spacing: (String text) -> String
|
|
55
|
+
|
|
56
|
+
# Ensure proper footer spacing
|
|
57
|
+
def ensure_footer_spacing: (String text) -> String
|
|
58
|
+
|
|
59
|
+
# Detect initial compare base from changelog
|
|
60
|
+
def detect_initial_compare_base: (Array[String] lines) -> String
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# TypeProf 0.21.11
|
|
2
|
+
|
|
3
|
+
module Kettle
|
|
4
|
+
module Dev
|
|
5
|
+
# Shared utilities for working with Prism AST nodes
|
|
6
|
+
module PrismUtils
|
|
7
|
+
# Parse Ruby source code and return Prism parse result with comments
|
|
8
|
+
def self.parse_with_comments: (String source) -> Prism::ParseResult
|
|
9
|
+
|
|
10
|
+
# Extract statements from a Prism body node
|
|
11
|
+
def self.extract_statements: (Prism::Node? body_node) -> Array[Prism::Node]
|
|
12
|
+
|
|
13
|
+
# Generate a unique key for a statement node to identify equivalent statements
|
|
14
|
+
def self.statement_key: (
|
|
15
|
+
Prism::Node node,
|
|
16
|
+
?tracked_methods: Array[Symbol]
|
|
17
|
+
) -> [Symbol, String?]?
|
|
18
|
+
|
|
19
|
+
# Extract literal value from string or symbol nodes
|
|
20
|
+
def self.extract_literal_value: (Prism::Node? node) -> (String | Symbol)?
|
|
21
|
+
|
|
22
|
+
# Extract qualified constant name from a constant node
|
|
23
|
+
def self.extract_const_name: (Prism::Node? node) -> String?
|
|
24
|
+
|
|
25
|
+
# Find leading comments for a statement node
|
|
26
|
+
def self.find_leading_comments: (
|
|
27
|
+
Prism::ParseResult parse_result,
|
|
28
|
+
Prism::Node current_stmt,
|
|
29
|
+
Prism::Node? prev_stmt,
|
|
30
|
+
Prism::Node body_node
|
|
31
|
+
) -> Array[Prism::Comment]
|
|
32
|
+
|
|
33
|
+
# Find inline comments for a statement node
|
|
34
|
+
def self.inline_comments_for_node: (
|
|
35
|
+
Prism::ParseResult parse_result,
|
|
36
|
+
Prism::Node stmt
|
|
37
|
+
) -> Array[Prism::Comment]
|
|
38
|
+
|
|
39
|
+
# Convert a Prism AST node to Ruby source code
|
|
40
|
+
def self.node_to_source: (Prism::Node? node) -> String
|
|
41
|
+
|
|
42
|
+
# Normalize a call node to use parentheses format
|
|
43
|
+
def self.normalize_call_node: (Prism::Node node) -> String
|
|
44
|
+
|
|
45
|
+
# Normalize an argument node to canonical format
|
|
46
|
+
def self.normalize_argument: (Prism::Node arg) -> String
|
|
47
|
+
|
|
48
|
+
# Check if a node is a specific method call
|
|
49
|
+
def self.call_to?: (Prism::Node node, Symbol method_name) -> bool
|
|
50
|
+
|
|
51
|
+
# Check if a node is a block call to a specific method
|
|
52
|
+
def self.block_call_to?: (Prism::Node node, Symbol method_name) -> bool
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# TypeProf 0.21.11
|
|
2
|
+
|
|
3
|
+
module Kettle
|
|
4
|
+
module Dev
|
|
5
|
+
# Prism-based AST merging for templated Ruby files
|
|
6
|
+
module SourceMerger
|
|
7
|
+
FREEZE_START: Regexp
|
|
8
|
+
FREEZE_END: Regexp
|
|
9
|
+
FREEZE_BLOCK: Regexp
|
|
10
|
+
FREEZE_REMINDER: String
|
|
11
|
+
BUG_URL: String
|
|
12
|
+
|
|
13
|
+
# Apply a templating strategy to merge source and destination Ruby files
|
|
14
|
+
def self.apply: (
|
|
15
|
+
strategy: Symbol,
|
|
16
|
+
src: String,
|
|
17
|
+
dest: String,
|
|
18
|
+
path: String
|
|
19
|
+
) -> String
|
|
20
|
+
|
|
21
|
+
# Ensure freeze reminder comment is present at the top of content
|
|
22
|
+
def self.ensure_reminder: (String content) -> String
|
|
23
|
+
|
|
24
|
+
# Normalize source code while preserving formatting
|
|
25
|
+
def self.normalize_source: (String source) -> String
|
|
26
|
+
|
|
27
|
+
# Check if freeze reminder is present in content
|
|
28
|
+
def self.reminder_present?: (String content) -> bool
|
|
29
|
+
|
|
30
|
+
# Find index where freeze reminder should be inserted
|
|
31
|
+
def self.reminder_insertion_index: (String content) -> Integer
|
|
32
|
+
|
|
33
|
+
# Check if line is a shebang
|
|
34
|
+
def self.shebang?: (String line) -> bool
|
|
35
|
+
|
|
36
|
+
# Check if line is a frozen_string_literal comment
|
|
37
|
+
def self.frozen_comment?: (String line) -> bool
|
|
38
|
+
|
|
39
|
+
# Merge kettle-dev:freeze blocks from destination into source content
|
|
40
|
+
def self.merge_freeze_blocks: (String src_content, String dest_content) -> String
|
|
41
|
+
|
|
42
|
+
# Extract freeze blocks from text
|
|
43
|
+
def self.freeze_blocks: (String? text) -> Array[Hash[Symbol, untyped]]
|
|
44
|
+
|
|
45
|
+
# Normalize strategy symbol
|
|
46
|
+
def self.normalize_strategy: (Symbol? strategy) -> Symbol
|
|
47
|
+
|
|
48
|
+
# Warn about bugs and print error information
|
|
49
|
+
def self.warn_bug: (String path, StandardError error) -> void
|
|
50
|
+
|
|
51
|
+
# Ensure text ends with newline
|
|
52
|
+
def self.ensure_trailing_newline: (String? text) -> String
|
|
53
|
+
|
|
54
|
+
# Apply append strategy
|
|
55
|
+
def self.apply_append: (String src_content, String dest_content) -> String
|
|
56
|
+
|
|
57
|
+
# Apply merge strategy
|
|
58
|
+
def self.apply_merge: (String src_content, String dest_content) -> String
|
|
59
|
+
|
|
60
|
+
# Merge node information
|
|
61
|
+
def self.merge_node_info: (Array[untyped] signature, Hash[Symbol, untyped] dest_node_info, Hash[Symbol, untyped] src_node_info) -> Hash[Symbol, untyped]
|
|
62
|
+
|
|
63
|
+
# Merge block node information
|
|
64
|
+
def self.merge_block_node_info: (Hash[Symbol, untyped] src_node_info) -> Hash[Symbol, untyped]
|
|
65
|
+
|
|
66
|
+
# Perform Prism-based merge with block
|
|
67
|
+
def self.prism_merge: (String src_content, String dest_content) { (Array[Hash[Symbol, untyped]], Array[Hash[Symbol, untyped]], Prism::ParseResult, Prism::ParseResult) -> Array[Hash[Symbol, untyped]] } -> String
|
|
68
|
+
|
|
69
|
+
# Extract nodes with comments from parse result
|
|
70
|
+
def self.extract_nodes_with_comments: (Prism::ParseResult parse_result) -> Array[Hash[Symbol, untyped]]
|
|
71
|
+
|
|
72
|
+
# Build source from node information array
|
|
73
|
+
def self.build_source_from_nodes: (Array[Hash[Symbol, untyped]] node_infos) -> String
|
|
74
|
+
|
|
75
|
+
# Generate signature for node
|
|
76
|
+
def self.node_signature: (Prism::Node? node) -> Array[untyped]
|
|
77
|
+
|
|
78
|
+
# Restore custom leading comments from destination
|
|
79
|
+
def self.restore_custom_leading_comments: (String dest_content, String merged_content) -> String
|
|
80
|
+
|
|
81
|
+
# Extract leading comment block from content
|
|
82
|
+
def self.leading_comment_block: (String content) -> String
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# TypeProf 0.21.11
|
|
2
|
+
|
|
3
|
+
module Kettle
|
|
4
|
+
module Dev
|
|
5
|
+
# Shared helpers for version detection and bump classification
|
|
6
|
+
module Versioning
|
|
7
|
+
# Detects a unique VERSION constant declared under lib/**/version.rb
|
|
8
|
+
def self.detect_version: (String root) -> String
|
|
9
|
+
|
|
10
|
+
# Classify the bump type from prev -> cur
|
|
11
|
+
def self.classify_bump: (String prev, String cur) -> Symbol
|
|
12
|
+
|
|
13
|
+
# Whether MAJOR is an EPIC version (strictly > 1000)
|
|
14
|
+
def self.epic_major?: (Integer major) -> bool
|
|
15
|
+
|
|
16
|
+
# Abort via ExitAdapter if available; otherwise Kernel.abort
|
|
17
|
+
def self.abort!: (String msg) -> void
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
data.tar.gz.sig
CHANGED
|
Binary file
|