Module: Philiprehberger::EnvDiff

Defined in:
lib/philiprehberger/env_diff.rb,
lib/philiprehberger/env_diff/diff.rb,
lib/philiprehberger/env_diff/parser.rb,
lib/philiprehberger/env_diff/version.rb

Defined Under Namespace

Modules: Parser Classes: Diff, Error

Constant Summary collapse

VERSION =
'0.5.0'

Class Method Summary collapse

Class Method Details

.compare(source, target, case_sensitive: true) ⇒ Diff

Compare two environment hashes and return a Diff.

Parameters:

  • source (Hash)

    the baseline environment hash

  • target (Hash)

    the environment hash to compare against

  • case_sensitive (Boolean) (defaults to: true)

    whether key comparison is case-sensitive (default: true)

Returns:

  • (Diff)

    the computed differences



17
18
19
20
21
22
23
24
25
# File 'lib/philiprehberger/env_diff.rb', line 17

def self.compare(source, target, case_sensitive: true)
  if case_sensitive
    Diff.new(source, target)
  else
    normalized_source = source.transform_keys(&:upcase)
    normalized_target = target.transform_keys(&:upcase)
    Diff.new(normalized_source, normalized_target)
  end
end

.from_env_file(path_a, path_b, case_sensitive: true) ⇒ Diff

Parse two .env files and compare them.

Parameters:

  • path_a (String)

    path to the source .env file

  • path_b (String)

    path to the target .env file

  • case_sensitive (Boolean) (defaults to: true)

    whether key comparison is case-sensitive (default: true)

Returns:

  • (Diff)

    the computed differences



155
156
157
# File 'lib/philiprehberger/env_diff.rb', line 155

def self.from_env_file(path_a, path_b, case_sensitive: true)
  compare(Parser.parse_file(path: path_a), Parser.parse_file(path: path_b), case_sensitive: case_sensitive)
end

.from_hash(hash_a, hash_b, case_sensitive: true) ⇒ Diff

Alias for compare — compare two hashes.

Parameters:

  • hash_a (Hash)

    the baseline environment hash

  • hash_b (Hash)

    the environment hash to compare against

  • case_sensitive (Boolean) (defaults to: true)

    whether key comparison is case-sensitive (default: true)

Returns:

  • (Diff)

    the computed differences



145
146
147
# File 'lib/philiprehberger/env_diff.rb', line 145

def self.from_hash(hash_a, hash_b, case_sensitive: true)
  compare(hash_a, hash_b, case_sensitive: case_sensitive)
end

.from_system(target, case_sensitive: true) ⇒ Diff

Compare the current system ENV against a target hash or .env file path.

Parameters:

  • target (Hash, String)

    a hash of target variables or a path to a .env file

  • case_sensitive (Boolean) (defaults to: true)

    whether key comparison is case-sensitive (default: true)

Returns:

  • (Diff)

    the computed differences between ENV and target



164
165
166
167
# File 'lib/philiprehberger/env_diff.rb', line 164

def self.from_system(target, case_sensitive: true)
  target_hash = target.is_a?(String) ? Parser.parse_file(path: target) : target
  compare(ENV.to_h, target_hash, case_sensitive: case_sensitive)
end

.to_csv(diff, mask: []) ⇒ String

Format a diff result as a CSV string with header ‘key,status,source,target`.

Values containing commas, quotes, or newlines are quoted per RFC 4180. Keys whose name contains any of ‘mask` (substring match, case-insensitive) have their source and target values redacted to `***`.

Parameters:

  • diff (Diff)

    the diff to format

  • mask (Array<String>) (defaults to: [])

    substrings to match against keys for redaction

Returns:

  • (String)

    CSV body with a trailing newline



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/philiprehberger/env_diff.rb', line 75

def self.to_csv(diff, mask: [])
  data = diff.to_h
  lines = ['key,status,source,target']

  diff.added.each { |key| lines << csv_row(key, 'added', '', data[:added][key], mask) }
  diff.removed.each { |key| lines << csv_row(key, 'removed', data[:removed][key], '', mask) }
  diff.changed.each do |key, vals|
    lines << csv_row(key, 'changed', vals[:source], vals[:target], mask)
  end
  diff.unchanged.each do |key|
    val = data[:unchanged][key]
    lines << csv_row(key, 'unchanged', val, val, mask)
  end

  "#{lines.join("\n")}\n"
end

.to_html(diff) ⇒ String

Format a diff result as an HTML table string.

Parameters:

  • diff (Diff)

    the diff to format

Returns:

  • (String)

    HTML table



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/philiprehberger/env_diff.rb', line 113

def self.to_html(diff)
  rows = diff.added.map do |key|
    "  <tr><td>#{key}</td><td>added</td><td></td><td>#{diff.to_h[:added][key]}</td></tr>"
  end

  diff.removed.each do |key|
    rows << "  <tr><td>#{key}</td><td>removed</td><td>#{diff.to_h[:removed][key]}</td><td></td></tr>"
  end

  diff.changed.each do |key, vals|
    rows << "  <tr><td>#{key}</td><td>changed</td><td>#{vals[:source]}</td><td>#{vals[:target]}</td></tr>"
  end

  diff.unchanged.each do |key|
    val = diff.to_h[:unchanged][key]
    rows << "  <tr><td>#{key}</td><td>unchanged</td><td>#{val}</td><td>#{val}</td></tr>"
  end

  lines = []
  lines << '<table>'
  lines << '  <tr><th>Key</th><th>Status</th><th>Source</th><th>Target</th></tr>'
  lines.concat(rows)
  lines << '</table>'
  lines.join("\n")
end

.to_markdown(diff) ⇒ String

Format a diff result as a Markdown table string.

Parameters:

  • diff (Diff)

    the diff to format

Returns:

  • (String)

    Markdown table



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/philiprehberger/env_diff.rb', line 42

def self.to_markdown(diff)
  lines = []
  lines << '| Key | Status | Source | Target |'
  lines << '| --- | ------ | ------ | ------ |'

  diff.added.each do |key|
    lines << "| #{key} | added | | #{diff.to_h[:added][key]} |"
  end

  diff.removed.each do |key|
    lines << "| #{key} | removed | #{diff.to_h[:removed][key]} | |"
  end

  diff.changed.each do |key, vals|
    lines << "| #{key} | changed | #{vals[:source]} | #{vals[:target]} |"
  end

  diff.unchanged.each do |key|
    lines << "| #{key} | unchanged | #{diff.to_h[:unchanged][key]} | #{diff.to_h[:unchanged][key]} |"
  end

  lines.join("\n")
end

.validate(target, required:) ⇒ Hash

Validate that all required keys exist in target.

Parameters:

  • target (Hash, String)

    a hash of target variables or a path to a .env file

  • required (Array<String>)

    list of required key names

Returns:

  • (Hash)

    with :valid (Boolean) and :missing (Array<String>)



32
33
34
35
36
# File 'lib/philiprehberger/env_diff.rb', line 32

def self.validate(target, required:)
  target_hash = target.is_a?(String) ? Parser.parse_file(path: target) : target
  missing = required.reject { |key| target_hash.key?(key) }
  { valid: missing.empty?, missing: missing }
end