philiprehberger-env_diff 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 256215c35e39e22093122c4c9d2452e3298beb699cbb84189a14dbf88ce75dbb
4
- data.tar.gz: ddada81c036244804246bd9e8c5c790778a9621bf412aef912cedf15ecd4e116
3
+ metadata.gz: cb5b721f4c0ca4625cc7065f8e443aeaeb069394759e21829141bc2e551180c4
4
+ data.tar.gz: 44866b77b4a8adeab0ac1550d1a53b995875cc444fc1fd67a7deb32bd1216035
5
5
  SHA512:
6
- metadata.gz: ef02926fa7b051d80cde13876739650db6c3279a8f068836292ff2676633668cdabc6256e6c50ca82a37f4a806ed942deebdbecd703f210dcfcce14f0171db4e
7
- data.tar.gz: 9d991cd745f294cb1d989144ba6136d6f64a52af32d7107d05d38193bca92a6740add34ad57e43dae9fa4103f1256f1d6852fbb87e56ca44d9cb371092b1a081
6
+ metadata.gz: 8e35a278641cbd4e7adaf9fe571e096a9ebd3a2521c56aed09750025e04265ddc743a8551d0efd94c5fb5b190b789fca7a9e995aad0deadef4dbffe973d5d04a
7
+ data.tar.gz: 606bed2477c6205c621a5e4e728eeb6f22d8dc3e6f671ac66d7d3077a1948aed414cda0570207b5b5019a32a94e14cb40b4aed48c4b940fd0f5f4f0b02c03e69
data/CHANGELOG.md CHANGED
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.0] - 2026-04-14
11
+
12
+ ### Added
13
+ - `EnvDiff.validate(target, required:)` — check that all required keys exist in a target hash or .env file
14
+ - `case_sensitive:` keyword on `compare`, `from_hash`, `from_env_file`, and `from_system` — normalize keys to uppercase when false
15
+ - `EnvDiff.to_markdown(diff)` — format a diff result as a Markdown table string
16
+ - `EnvDiff.to_html(diff)` — format a diff result as an HTML table string
17
+
10
18
  ## [0.2.0] - 2026-04-03
11
19
 
12
20
  ### Added
data/README.md CHANGED
@@ -82,6 +82,57 @@ diff.stats
82
82
  # => { added: 1, removed: 1, changed: 1, unchanged: 1, total: 4 }
83
83
  ```
84
84
 
85
+ ### Validation
86
+
87
+ Check that all required keys exist in a target hash or `.env` file:
88
+
89
+ ```ruby
90
+ target = { 'DATABASE_URL' => 'postgres://localhost', 'PORT' => '3000' }
91
+ result = Philiprehberger::EnvDiff.validate(target, required: %w[DATABASE_URL SECRET PORT])
92
+ result[:valid] # => false
93
+ result[:missing] # => ["SECRET"]
94
+
95
+ # Also works with .env file paths
96
+ result = Philiprehberger::EnvDiff.validate(".env.production", required: %w[DATABASE_URL SECRET])
97
+ ```
98
+
99
+ ### Case-Insensitive Comparison
100
+
101
+ Normalize keys to uppercase before comparing by passing `case_sensitive: false`:
102
+
103
+ ```ruby
104
+ diff = Philiprehberger::EnvDiff.compare(
105
+ { "db_host" => "localhost" },
106
+ { "DB_HOST" => "localhost" },
107
+ case_sensitive: false
108
+ )
109
+ diff.changed? # => false
110
+ diff.unchanged # => ["DB_HOST"]
111
+ ```
112
+
113
+ ### Export Formats
114
+
115
+ Format a diff result as a Markdown or HTML table:
116
+
117
+ ```ruby
118
+ diff = Philiprehberger::EnvDiff.compare(source, target)
119
+
120
+ puts Philiprehberger::EnvDiff.to_markdown(diff)
121
+ # | Key | Status | Source | Target |
122
+ # | --- | ------ | ------ | ------ |
123
+ # | NEW_KEY | added | | added |
124
+ # | OLD_KEY | removed | remove_me | |
125
+ # | DATABASE_URL | changed | postgres://localhost/dev | postgres://prod-host/app |
126
+ # | SECRET | unchanged | abc | abc |
127
+
128
+ puts Philiprehberger::EnvDiff.to_html(diff)
129
+ # <table>
130
+ # <tr><th>Key</th><th>Status</th><th>Source</th><th>Target</th></tr>
131
+ # <tr><td>NEW_KEY</td><td>added</td><td></td><td>added</td></tr>
132
+ # ...
133
+ # </table>
134
+ ```
135
+
85
136
  ### Compare against system ENV
86
137
 
87
138
  ```ruby
@@ -112,10 +163,13 @@ ENV
112
163
 
113
164
  | Method / Class | Description |
114
165
  |----------------|-------------|
115
- | `EnvDiff.compare(source, target)` | Compare two hashes and return a `Diff` |
116
- | `EnvDiff.from_hash(hash_a, hash_b)` | Alias for `compare` |
117
- | `EnvDiff.from_env_file(path_a, path_b)` | Parse two `.env` files and compare them |
118
- | `EnvDiff.from_system(target)` | Compare current `ENV` against a target hash or `.env` file path |
166
+ | `EnvDiff.compare(source, target, case_sensitive: true)` | Compare two hashes and return a `Diff` |
167
+ | `EnvDiff.from_hash(hash_a, hash_b, case_sensitive: true)` | Alias for `compare` |
168
+ | `EnvDiff.from_env_file(path_a, path_b, case_sensitive: true)` | Parse two `.env` files and compare them |
169
+ | `EnvDiff.from_system(target, case_sensitive: true)` | Compare current `ENV` against a target hash or `.env` file path |
170
+ | `EnvDiff.validate(target, required:)` | Check that all required keys exist in target; returns `{ valid:, missing: }` |
171
+ | `EnvDiff.to_markdown(diff)` | Format a diff result as a Markdown table string |
172
+ | `EnvDiff.to_html(diff)` | Format a diff result as an HTML table string |
119
173
  | `Diff#added` | Array of keys in target but not source |
120
174
  | `Diff#removed` | Array of keys in source but not target |
121
175
  | `Diff#changed` | Hash of keys with different values |
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  module EnvDiff
5
- VERSION = '0.2.0'
5
+ VERSION = '0.3.0'
6
6
  end
7
7
  end
@@ -12,36 +12,115 @@ module Philiprehberger
12
12
  #
13
13
  # @param source [Hash] the baseline environment hash
14
14
  # @param target [Hash] the environment hash to compare against
15
+ # @param case_sensitive [Boolean] whether key comparison is case-sensitive (default: true)
15
16
  # @return [Diff] the computed differences
16
- def self.compare(source, target)
17
- Diff.new(source, target)
17
+ def self.compare(source, target, case_sensitive: true)
18
+ if case_sensitive
19
+ Diff.new(source, target)
20
+ else
21
+ normalized_source = source.transform_keys(&:upcase)
22
+ normalized_target = target.transform_keys(&:upcase)
23
+ Diff.new(normalized_source, normalized_target)
24
+ end
25
+ end
26
+
27
+ # Validate that all required keys exist in target.
28
+ #
29
+ # @param target [Hash, String] a hash of target variables or a path to a .env file
30
+ # @param required [Array<String>] list of required key names
31
+ # @return [Hash] with :valid (Boolean) and :missing (Array<String>)
32
+ def self.validate(target, required:)
33
+ target_hash = target.is_a?(String) ? Parser.parse_file(path: target) : target
34
+ missing = required.reject { |key| target_hash.key?(key) }
35
+ { valid: missing.empty?, missing: missing }
36
+ end
37
+
38
+ # Format a diff result as a Markdown table string.
39
+ #
40
+ # @param diff [Diff] the diff to format
41
+ # @return [String] Markdown table
42
+ def self.to_markdown(diff)
43
+ lines = []
44
+ lines << '| Key | Status | Source | Target |'
45
+ lines << '| --- | ------ | ------ | ------ |'
46
+
47
+ diff.added.each do |key|
48
+ lines << "| #{key} | added | | #{diff.to_h[:added][key]} |"
49
+ end
50
+
51
+ diff.removed.each do |key|
52
+ lines << "| #{key} | removed | #{diff.to_h[:removed][key]} | |"
53
+ end
54
+
55
+ diff.changed.each do |key, vals|
56
+ lines << "| #{key} | changed | #{vals[:source]} | #{vals[:target]} |"
57
+ end
58
+
59
+ diff.unchanged.each do |key|
60
+ lines << "| #{key} | unchanged | #{diff.to_h[:unchanged][key]} | #{diff.to_h[:unchanged][key]} |"
61
+ end
62
+
63
+ lines.join("\n")
64
+ end
65
+
66
+ # Format a diff result as an HTML table string.
67
+ #
68
+ # @param diff [Diff] the diff to format
69
+ # @return [String] HTML table
70
+ def self.to_html(diff)
71
+ rows = diff.added.map do |key|
72
+ " <tr><td>#{key}</td><td>added</td><td></td><td>#{diff.to_h[:added][key]}</td></tr>"
73
+ end
74
+
75
+ diff.removed.each do |key|
76
+ rows << " <tr><td>#{key}</td><td>removed</td><td>#{diff.to_h[:removed][key]}</td><td></td></tr>"
77
+ end
78
+
79
+ diff.changed.each do |key, vals|
80
+ rows << " <tr><td>#{key}</td><td>changed</td><td>#{vals[:source]}</td><td>#{vals[:target]}</td></tr>"
81
+ end
82
+
83
+ diff.unchanged.each do |key|
84
+ val = diff.to_h[:unchanged][key]
85
+ rows << " <tr><td>#{key}</td><td>unchanged</td><td>#{val}</td><td>#{val}</td></tr>"
86
+ end
87
+
88
+ lines = []
89
+ lines << '<table>'
90
+ lines << ' <tr><th>Key</th><th>Status</th><th>Source</th><th>Target</th></tr>'
91
+ lines.concat(rows)
92
+ lines << '</table>'
93
+ lines.join("\n")
18
94
  end
19
95
 
20
96
  # Alias for compare — compare two hashes.
21
97
  #
22
98
  # @param hash_a [Hash] the baseline environment hash
23
99
  # @param hash_b [Hash] the environment hash to compare against
100
+ # @param case_sensitive [Boolean] whether key comparison is case-sensitive (default: true)
24
101
  # @return [Diff] the computed differences
25
- def self.from_hash(hash_a, hash_b)
26
- compare(hash_a, hash_b)
102
+ def self.from_hash(hash_a, hash_b, case_sensitive: true)
103
+ compare(hash_a, hash_b, case_sensitive: case_sensitive)
27
104
  end
28
105
 
29
106
  # Parse two .env files and compare them.
30
107
  #
31
108
  # @param path_a [String] path to the source .env file
32
109
  # @param path_b [String] path to the target .env file
110
+ # @param case_sensitive [Boolean] whether key comparison is case-sensitive (default: true)
33
111
  # @return [Diff] the computed differences
34
- def self.from_env_file(path_a, path_b)
35
- compare(Parser.parse_file(path: path_a), Parser.parse_file(path: path_b))
112
+ def self.from_env_file(path_a, path_b, case_sensitive: true)
113
+ compare(Parser.parse_file(path: path_a), Parser.parse_file(path: path_b), case_sensitive: case_sensitive)
36
114
  end
37
115
 
38
116
  # Compare the current system ENV against a target hash or .env file path.
39
117
  #
40
118
  # @param target [Hash, String] a hash of target variables or a path to a .env file
119
+ # @param case_sensitive [Boolean] whether key comparison is case-sensitive (default: true)
41
120
  # @return [Diff] the computed differences between ENV and target
42
- def self.from_system(target)
121
+ def self.from_system(target, case_sensitive: true)
43
122
  target_hash = target.is_a?(String) ? Parser.parse_file(path: target) : target
44
- compare(ENV.to_h, target_hash)
123
+ compare(ENV.to_h, target_hash, case_sensitive: case_sensitive)
45
124
  end
46
125
  end
47
126
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: philiprehberger-env_diff
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Philip Rehberger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-04-04 00:00:00.000000000 Z
11
+ date: 2026-04-15 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Parse .env files or environment hashes, compare them, and get a clear
14
14
  report of added, removed, changed, and unchanged variables.