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



1968
1969
1970
1971
1972
1973
# File 'lib/kettle/jem.rb', line 1968

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



1975
1976
1977
1978
1979
1980
1981
1982
# File 'lib/kettle/jem.rb', line 1975

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



1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
# File 'lib/kettle/jem.rb', line 1984

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



1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
# File 'lib/kettle/jem.rb', line 1889

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



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

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



1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
# File 'lib/kettle/jem.rb', line 1934

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



1945
1946
1947
# File 'lib/kettle/jem.rb', line 1945

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

.load_state(config_path:) ⇒ Object



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

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



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

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



2016
2017
2018
2019
2020
2021
2022
# File 'lib/kettle/jem.rb', line 2016

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



1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
# File 'lib/kettle/jem.rb', line 1921

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



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

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



2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
# File 'lib/kettle/jem.rb', line 2002

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:



2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
# File 'lib/kettle/jem.rb', line 2024

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