Class: Kettle::Changelog::KeyedEntryUpserter

Inherits:
Object
  • Object
show all
Defined in:
lib/kettle/changelog/keyed_entry_upserter.rb

Constant Summary collapse

SECTIONS =
%w[Added Changed Deprecated Removed Fixed Security].freeze
KEY_PREFIX =
"[kc]"
PROJECT_FILE_UPDATE_FIRST_LINE =
/\A[-*]\s+\[kc\]\s+([^\s:]+):\s+updated\s+\d+\s+project\s+files?:\s*\z/
PROJECT_FILE_UPDATE_CATEGORY_LINE =
/\A\s{2,}[-*]\s+(.+?)\s+\((\d+)\)\s*\z/

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(section:, key:, entry:, legacy_prefixes: [], root: Kettle::Dev::CIHelpers.project_root) ⇒ KeyedEntryUpserter

Returns a new instance of KeyedEntryUpserter.



70
71
72
73
74
75
76
77
# File 'lib/kettle/changelog/keyed_entry_upserter.rb', line 70

def initialize(section:, key:, entry:, legacy_prefixes: [], root: Kettle::Dev::CIHelpers.project_root)
  @root = root
  @section = section.to_s
  @key = key.to_s.strip
  @entry = entry
  @legacy_prefixes = Array(legacy_prefixes).map { |prefix| prefix.to_s.strip }.reject(&:empty?)
  @changelog_path = ENV.fetch("K_CHANGELOG_PATH", File.join(@root, "CHANGELOG.md"))
end

Class Method Details

.collapse_project_file_updates(source) ⇒ Object

Consolidates repeated maintenance summaries after a prepared release is updated in place. The AST supplies list-item boundaries; the two regular expressions only parse the stable text payload inside those already-classified Markdown items.



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/kettle/changelog/keyed_entry_upserter.rb', line 16

def collapse_project_file_updates(source)
  require "ast/crispr/markdown/markly"
  context = Ast::Crispr::Markdown::Markly.document_context(content: source.to_s, source_label: "CHANGELOG.md")
  updates = context.structural_owners(owner_scope: :list_items)
    .select { |item| item.depth == 1 }
    .filter_map { |item| project_file_update_record(item) }
  duplicate_groups = updates.group_by { |update| update.fetch(:key) }.values.select { |group| group.length > 1 }
  return source.to_s if duplicate_groups.empty?

  lines = source.to_s.lines
  duplicate_groups.flatten.sort_by { |update| update.fetch(:start_line) }.reverse_each do |update|
    group = duplicate_groups.find { |candidate| candidate.include?(update) }
    first = group.min_by { |candidate| candidate.fetch(:start_line) }
    replacement = (update == first) ? project_file_update_entry(group).lines : []
    lines[(update.fetch(:start_line) - 1)...update.fetch(:end_line)] = replacement
  end
  lines.join
end

Instance Method Details

#runObject



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/kettle/changelog/keyed_entry_upserter.rb', line 79

def run
  validate!
  source = read_source!
  matches = find_matches(source)
  if matches.count { |match| match.fetch(:kind) == :keyed } > 1
    raise Error, "expected at most one #{key_label} entry in CHANGELOG.md"
  end
  if matches.any? { |match| match.fetch(:kind) == :keyed } && matches.any? { |match| match.fetch(:kind) == :legacy }
    raise Error, "found both keyed and legacy entries for #{key_label} in CHANGELOG.md"
  end

  body = @entry.respond_to?(:call) ? @entry.call(matches) : @entry
  rendered = render_entry(body)
  changed = matches.empty? || matches.any? { |match| normalize_source(match.fetch(:source)) != normalize_source(rendered) } || matches.length > 1
  write_updated_source(source, rendered, matches) if changed

  matched = if matches.empty?
    "inserted"
  elsif matches.first.fetch(:kind) == :legacy
    "legacy"
  else
    "keyed"
  end

  {
    changed: changed,
    key: @key,
    section: @section,
    entry: rendered,
    matched: matched
  }
end