philiprehberger-env_diff 0.3.0 → 0.5.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: cb5b721f4c0ca4625cc7065f8e443aeaeb069394759e21829141bc2e551180c4
4
- data.tar.gz: 44866b77b4a8adeab0ac1550d1a53b995875cc444fc1fd67a7deb32bd1216035
3
+ metadata.gz: 8fa424560bb3f733e97c91a5e643f04306eb81aa032184970889e71d987910c6
4
+ data.tar.gz: 32748ed41ac378a1dd96fdf74a91ebd1f7e72fa74628510cd2fcdbb61e4d3c74
5
5
  SHA512:
6
- metadata.gz: 8e35a278641cbd4e7adaf9fe571e096a9ebd3a2521c56aed09750025e04265ddc743a8551d0efd94c5fb5b190b789fca7a9e995aad0deadef4dbffe973d5d04a
7
- data.tar.gz: 606bed2477c6205c621a5e4e728eeb6f22d8dc3e6f671ac66d7d3077a1948aed414cda0570207b5b5019a32a94e14cb40b4aed48c4b940fd0f5f4f0b02c03e69
6
+ metadata.gz: 1738b36b798d79c436f4471ee7a44f755d8d83ef4d2ba89ffcee2f7724de895573cdc3b198f7943c3c9d49c8737a9cc1887b76a0c035129a8f9b047d882469a1
7
+ data.tar.gz: bf909ac4ba6905c9889ce456b7c29d1ef087618562954305a553f58690c0f635dc32c9a8278de0946936ac33e27d0019e3ee3510535c3621482913aff8878c6a
data/CHANGELOG.md CHANGED
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.5.0] - 2026-05-08
11
+
12
+ ### Added
13
+ - `EnvDiff.to_csv(diff, mask: [])` — exports a structured `key,status,source,target` CSV (RFC 4180 quoting). Complements the existing `to_markdown` and `to_html` exports for spreadsheet review. Supports the same `mask:` substring redaction as `Diff#summary`.
14
+
15
+ ## [0.4.0] - 2026-04-24
16
+
17
+ ### Added
18
+ - `Diff#status_for(key)` — returns the status of a single key as a Symbol (`:added`, `:removed`, `:changed`, `:unchanged`), or `nil` if the key is absent from both sides
19
+
10
20
  ## [0.3.0] - 2026-04-14
11
21
 
12
22
  ### Added
data/README.md CHANGED
@@ -82,6 +82,16 @@ diff.stats
82
82
  # => { added: 1, removed: 1, changed: 1, unchanged: 1, total: 4 }
83
83
  ```
84
84
 
85
+ ### Per-key status
86
+
87
+ ```ruby
88
+ diff.status_for("NEW_KEY") # => :added
89
+ diff.status_for("OLD_KEY") # => :removed
90
+ diff.status_for("DATABASE_URL") # => :changed
91
+ diff.status_for("SECRET") # => :unchanged
92
+ diff.status_for("NOT_THERE") # => nil
93
+ ```
94
+
85
95
  ### Validation
86
96
 
87
97
  Check that all required keys exist in a target hash or `.env` file:
@@ -112,7 +122,7 @@ diff.unchanged # => ["DB_HOST"]
112
122
 
113
123
  ### Export Formats
114
124
 
115
- Format a diff result as a Markdown or HTML table:
125
+ Format a diff result as a Markdown, HTML, or CSV table:
116
126
 
117
127
  ```ruby
118
128
  diff = Philiprehberger::EnvDiff.compare(source, target)
@@ -131,6 +141,13 @@ puts Philiprehberger::EnvDiff.to_html(diff)
131
141
  # <tr><td>NEW_KEY</td><td>added</td><td></td><td>added</td></tr>
132
142
  # ...
133
143
  # </table>
144
+
145
+ puts Philiprehberger::EnvDiff.to_csv(diff, mask: ['SECRET'])
146
+ # key,status,source,target
147
+ # NEW_KEY,added,,added
148
+ # OLD_KEY,removed,remove_me,
149
+ # DATABASE_URL,changed,postgres://localhost/dev,postgres://prod-host/app
150
+ # SECRET,unchanged,***,***
134
151
  ```
135
152
 
136
153
  ### Compare against system ENV
@@ -170,6 +187,7 @@ ENV
170
187
  | `EnvDiff.validate(target, required:)` | Check that all required keys exist in target; returns `{ valid:, missing: }` |
171
188
  | `EnvDiff.to_markdown(diff)` | Format a diff result as a Markdown table string |
172
189
  | `EnvDiff.to_html(diff)` | Format a diff result as an HTML table string |
190
+ | `EnvDiff.to_csv(diff, mask: [])` | Format a diff result as a `key,status,source,target` CSV; supports value masking |
173
191
  | `Diff#added` | Array of keys in target but not source |
174
192
  | `Diff#removed` | Array of keys in source but not target |
175
193
  | `Diff#changed` | Hash of keys with different values |
@@ -180,6 +198,7 @@ ENV
180
198
  | `Diff#to_json` | JSON serialization of the structured hash |
181
199
  | `Diff#filter(pattern:)` | New `Diff` containing only keys matching the regex pattern |
182
200
  | `Diff#stats` | Hash of counts: `:added`, `:removed`, `:changed`, `:unchanged`, `:total` |
201
+ | `Diff#status_for(key)` | Status of a single key as `:added`, `:removed`, `:changed`, `:unchanged`, or `nil` |
183
202
  | `Parser.parse(content)` | Parse `.env` string into a hash |
184
203
  | `Parser.parse_file(path:)` | Read and parse a `.env` file |
185
204
 
@@ -92,6 +92,19 @@ module Philiprehberger
92
92
  }
93
93
  end
94
94
 
95
+ # Status of a single key in this diff.
96
+ #
97
+ # @param key [String] the key to look up
98
+ # @return [Symbol, nil] one of :added, :removed, :changed, :unchanged, or nil if absent from both sides
99
+ def status_for(key)
100
+ return :added if @added.include?(key)
101
+ return :removed if @removed.include?(key)
102
+ return :changed if @changed.key?(key)
103
+ return :unchanged if @unchanged.include?(key)
104
+
105
+ nil
106
+ end
107
+
95
108
  private
96
109
 
97
110
  def build_changed(source, target)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  module EnvDiff
5
- VERSION = '0.3.0'
5
+ VERSION = '0.5.0'
6
6
  end
7
7
  end
@@ -63,6 +63,49 @@ module Philiprehberger
63
63
  lines.join("\n")
64
64
  end
65
65
 
66
+ # Format a diff result as a CSV string with header `key,status,source,target`.
67
+ #
68
+ # Values containing commas, quotes, or newlines are quoted per RFC 4180.
69
+ # Keys whose name contains any of `mask` (substring match, case-insensitive)
70
+ # have their source and target values redacted to `***`.
71
+ #
72
+ # @param diff [Diff] the diff to format
73
+ # @param mask [Array<String>] substrings to match against keys for redaction
74
+ # @return [String] CSV body with a trailing newline
75
+ def self.to_csv(diff, mask: [])
76
+ data = diff.to_h
77
+ lines = ['key,status,source,target']
78
+
79
+ diff.added.each { |key| lines << csv_row(key, 'added', '', data[:added][key], mask) }
80
+ diff.removed.each { |key| lines << csv_row(key, 'removed', data[:removed][key], '', mask) }
81
+ diff.changed.each do |key, vals|
82
+ lines << csv_row(key, 'changed', vals[:source], vals[:target], mask)
83
+ end
84
+ diff.unchanged.each do |key|
85
+ val = data[:unchanged][key]
86
+ lines << csv_row(key, 'unchanged', val, val, mask)
87
+ end
88
+
89
+ "#{lines.join("\n")}\n"
90
+ end
91
+
92
+ def self.csv_row(key, status, source, target, mask)
93
+ masked = mask.any? { |m| key.to_s.downcase.include?(m.to_s.downcase) }
94
+ source = '***' if masked && source.to_s != ''
95
+ target = '***' if masked && target.to_s != ''
96
+ [key, status, source, target].map { |cell| csv_escape(cell) }.join(',')
97
+ end
98
+ private_class_method :csv_row
99
+
100
+ def self.csv_escape(value)
101
+ string = value.to_s
102
+ return string unless string.match?(/[",\n\r]/)
103
+
104
+ escaped = string.gsub('"', '""')
105
+ "\"#{escaped}\""
106
+ end
107
+ private_class_method :csv_escape
108
+
66
109
  # Format a diff result as an HTML table string.
67
110
  #
68
111
  # @param diff [Diff] the diff to format
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.3.0
4
+ version: 0.5.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-15 00:00:00.000000000 Z
11
+ date: 2026-05-08 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.