Module: Kettle::Jem::TemplateChecksums

Defined in:
lib/kettle/jem.rb

Constant Summary collapse

YAML_KEY =
"kettle-jem"
CHECKSUMS_SUBKEY =
"checksums"
VERSION_SUBKEY =
"version"
APPLIED_AT_SUBKEY =
"applied_at"
CHANGELOG_REPLAY_SUBKEY =
"changelog_replay"
LAST_ENTRY_KEY_SUBKEY =
"last_entry_key"
LAST_ENTRY_DATE_SUBKEY =
"last_entry_date"

Class Method Summary collapse

Class Method Details

.build_yaml_block(checksums:, version: nil, applied_at: nil, changelog_replay: nil) ⇒ Object



1911
1912
1913
1914
1915
1916
# File 'lib/kettle/jem.rb', line 1911

def build_yaml_block(checksums:, version: nil, applied_at: nil, changelog_replay: nil)
  lines = [YAML_KEY]
  lines[0] = "#{lines[0]}:"
  lines.concat(build_yaml_state_lines(checksums: checksums, version: version, applied_at: applied_at, changelog_replay: changelog_replay).map { |line| "  #{line}" })
  lines.join("\n")
end

.build_yaml_state(checksums:, version: nil, applied_at: nil, changelog_replay: nil) ⇒ Object



1918
1919
1920
1921
1922
1923
1924
1925
# File 'lib/kettle/jem.rb', line 1918

def build_yaml_state(checksums:, version: nil, applied_at: nil, changelog_replay: nil)
  build_yaml_state_lines(
    checksums: checksums,
    version: version,
    applied_at: applied_at,
    changelog_replay: changelog_replay
  ).join("\n")
end

.build_yaml_state_lines(checksums:, version: nil, applied_at: nil, changelog_replay: nil) ⇒ Object



1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
# File 'lib/kettle/jem.rb', line 1927

def build_yaml_state_lines(checksums:, version: nil, applied_at: nil, changelog_replay: nil)
  lines = []
  lines << "#{VERSION_SUBKEY}: #{version.to_s.dump}" if version
  lines << "#{APPLIED_AT_SUBKEY}: #{applied_at.to_s.dump}" if applied_at
  if changelog_replay.is_a?(Hash) && !changelog_replay.empty?
    lines << "#{CHANGELOG_REPLAY_SUBKEY}:"
    last_entry_key = changelog_replay[LAST_ENTRY_KEY_SUBKEY] || changelog_replay[:last_entry_key]
     = changelog_replay[LAST_ENTRY_DATE_SUBKEY] || changelog_replay[:last_entry_date]
    lines << "  #{LAST_ENTRY_KEY_SUBKEY}: #{last_entry_key.to_s.dump}" if last_entry_key
    lines << "  #{LAST_ENTRY_DATE_SUBKEY}: #{.to_s.dump}" if 
  end
  lines << "#{CHECKSUMS_SUBKEY}:"
  checksums.sort.each do |path, sha|
    lines << "  #{path.dump}: #{sha.dump}"
  end
  lines
end

.compute(template_root:) ⇒ Object



1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
# File 'lib/kettle/jem.rb', line 1832

def compute(template_root:)
  root = template_root.to_s.chomp("/")
  checksums = {}
  Find.find(root) do |path|
    next unless File.file?(path)

    relative_path = path.delete_prefix("#{root}/")
    checksums[relative_path] = Digest::SHA256.file(path).hexdigest
  end
  checksums.sort.to_h
end

.detail_lines(diff) ⇒ Object



1903
1904
1905
1906
1907
1908
1909
# File 'lib/kettle/jem.rb', line 1903

def detail_lines(diff)
  [
    *diff.fetch(:added, []).map { |path| "  + #{path}" },
    *diff.fetch(:changed, []).map { |path| "  ~ #{path}" },
    *diff.fetch(:removed, []).map { |path| "  - #{path}" }
  ]
end

.diff(current:, stored:) ⇒ Object



1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
# File 'lib/kettle/jem.rb', line 1877

def diff(current:, stored:)
  current_keys = current.keys.to_set
  stored_keys = stored.keys.to_set

  {
    added: (current_keys - stored_keys).sort,
    changed: (current_keys & stored_keys).select { |path| current[path] != stored[path] }.sort,
    removed: (stored_keys - current_keys).sort
  }
end

.diff_count(diff) ⇒ Object



1888
1889
1890
# File 'lib/kettle/jem.rb', line 1888

def diff_count(diff)
  diff.fetch(:added, []).size + diff.fetch(:changed, []).size + diff.fetch(:removed, []).size
end

.load_state(config_path:) ⇒ Object



1854
1855
1856
1857
1858
1859
1860
1861
1862
# File 'lib/kettle/jem.rb', line 1854

def load_state(config_path:)
  return {} unless File.exist?(config_path.to_s)

  data = YAML.safe_load_file(config_path.to_s, permitted_classes: [], aliases: false)
  entry = data.is_a?(Hash) ? data[YAML_KEY] : nil
  entry.is_a?(Hash) ? entry : {}
rescue
  {}
end

.load_stored(config_path:) ⇒ Object



1844
1845
1846
1847
1848
1849
1850
1851
1852
# File 'lib/kettle/jem.rb', line 1844

def load_stored(config_path:)
  return {} unless File.exist?(config_path.to_s)

  entry = load_state(config_path: config_path)
  stored = entry.is_a?(Hash) ? entry[CHECKSUMS_SUBKEY] : nil
  stored.is_a?(Hash) ? stored : {}
rescue
  {}
end

.merge_yaml_state(content, state_block) ⇒ Object



1959
1960
1961
1962
1963
1964
1965
# File 'lib/kettle/jem.rb', line 1959

def merge_yaml_state(content, state_block)
  range = yaml_state_node_range(content)
  return "#{content.to_s.rstrip}\n\n#{state_block}\n" unless range

  lines = content.to_s.lines
  [*lines[0...range.begin], "#{state_block}\n", *lines[range.end..].to_a].join
end

.remove_from_config(config_path:) ⇒ Object



1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
# File 'lib/kettle/jem.rb', line 1864

def remove_from_config(config_path:)
  return false unless File.exist?(config_path.to_s)

  content = File.read(config_path.to_s)
  range = yaml_state_node_range(content)
  return false unless range

  lines = content.to_s.lines
  updated = [*lines[0...range.begin], *lines[range.end..].to_a].join
  File.write(config_path.to_s, updated.gsub(/\n{3,}/, "\n\n"))
  true
end

.summary(diff) ⇒ Object



1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
# File 'lib/kettle/jem.rb', line 1892

def summary(diff)
  count = diff_count(diff)
  return "no template files changed since last run" if count.zero?

  parts = []
  parts << "#{diff.fetch(:added, []).size} added" if diff.fetch(:added, []).any?
  parts << "#{diff.fetch(:changed, []).size} changed" if diff.fetch(:changed, []).any?
  parts << "#{diff.fetch(:removed, []).size} removed" if diff.fetch(:removed, []).any?
  "#{count} template file(s) since last run: #{parts.join(", ")}"
end

.write_to_config(config_path:, checksums:, version: nil, applied_at: nil, changelog_replay: nil) ⇒ Object



1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
# File 'lib/kettle/jem.rb', line 1945

def write_to_config(config_path:, checksums:, version: nil, applied_at: nil, changelog_replay: nil)
  return unless File.exist?(config_path.to_s)

  content = File.read(config_path.to_s)
  state = build_yaml_state(
    checksums: checksums,
    version: version,
    applied_at: applied_at,
    changelog_replay: changelog_replay
  )
  updated = merge_yaml_state(content, "#{YAML_KEY}:\n#{state.lines.map { |line| "  #{line}" }.join}")
  File.write(config_path.to_s, updated)
end

.yaml_state_node_range(content) ⇒ Object

Raises:



1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
# File 'lib/kettle/jem.rb', line 1967

def yaml_state_node_range(content)
  require "yaml/merge"

  analysis = Yaml::Merge::FileAnalysis.new(content.to_s)
  raise Error, "could not parse kettle-jem config as YAML" unless analysis.valid?

  body = analysis.documents.first&.body_node
  return unless body&.mapping?

  pairs = body.mapping_pairs
  pairs.each_with_index do |pair, index|
    next unless pair.key_name == YAML_KEY

    start_index = pair.start_line.to_i - 1
    next_pair = pairs[index + 1]
    end_index = next_pair ? next_pair.start_line.to_i - 1 : [pair.end_line.to_i - 1, content.to_s.lines.length].min
    return start_index...end_index
  end

  nil
end