The project is in a healthy, maintained state
Parse .env files or environment hashes, compare them, and get a clear report of added, removed, changed, and unchanged variables.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies
 Project Readme

philiprehberger-env_diff

Tests Gem Version Last updated

Compare environment variables across environments and report differences

Requirements

  • Ruby >= 3.1

Installation

Add to your Gemfile:

gem "philiprehberger-env_diff"

Or install directly:

gem install philiprehberger-env_diff

Usage

require "philiprehberger/env_diff"

# Compare two hashes
source = { "DATABASE_URL" => "postgres://localhost/dev", "SECRET" => "abc", "OLD_KEY" => "remove_me" }
target = { "DATABASE_URL" => "postgres://prod-host/app", "SECRET" => "abc", "NEW_KEY" => "added" }

diff = Philiprehberger::EnvDiff.compare(source, target)

diff.added     # => ["NEW_KEY"]
diff.removed   # => ["OLD_KEY"]
diff.changed   # => { "DATABASE_URL" => { source: "postgres://localhost/dev", target: "postgres://prod-host/app" } }
diff.unchanged # => ["SECRET"]
diff.changed?  # => true
puts diff.summary
# + NEW_KEY
# - OLD_KEY
# ~ DATABASE_URL: "postgres://localhost/dev" -> "postgres://prod-host/app"

Masked summary

Hide sensitive values in the summary output using exact strings or regex patterns:

puts diff.summary(mask: ["SECRET", /PASSWORD|TOKEN/])
# + NEW_KEY
# - OLD_KEY
# ~ SECRET: *** -> ***

Structured output

diff.to_h
# => { added: { "NEW_KEY" => "added" }, removed: { "OLD_KEY" => "remove_me" },
#      changed: { "DATABASE_URL" => { source: "postgres://localhost/dev", target: "postgres://prod-host/app" } },
#      unchanged: { "SECRET" => "abc" } }

diff.to_json  # => JSON string of the structured hash

Filter by pattern

db_diff = diff.filter(pattern: /^DATABASE/)
db_diff.changed.keys  # => ["DATABASE_URL"]

Stats

diff.stats
# => { added: 1, removed: 1, changed: 1, unchanged: 1, total: 4 }

Validation

Check that all required keys exist in a target hash or .env file:

target = { 'DATABASE_URL' => 'postgres://localhost', 'PORT' => '3000' }
result = Philiprehberger::EnvDiff.validate(target, required: %w[DATABASE_URL SECRET PORT])
result[:valid]   # => false
result[:missing] # => ["SECRET"]

# Also works with .env file paths
result = Philiprehberger::EnvDiff.validate(".env.production", required: %w[DATABASE_URL SECRET])

Case-Insensitive Comparison

Normalize keys to uppercase before comparing by passing case_sensitive: false:

diff = Philiprehberger::EnvDiff.compare(
  { "db_host" => "localhost" },
  { "DB_HOST" => "localhost" },
  case_sensitive: false
)
diff.changed? # => false
diff.unchanged # => ["DB_HOST"]

Export Formats

Format a diff result as a Markdown or HTML table:

diff = Philiprehberger::EnvDiff.compare(source, target)

puts Philiprehberger::EnvDiff.to_markdown(diff)
# | Key | Status | Source | Target |
# | --- | ------ | ------ | ------ |
# | NEW_KEY | added | | added |
# | OLD_KEY | removed | remove_me | |
# | DATABASE_URL | changed | postgres://localhost/dev | postgres://prod-host/app |
# | SECRET | unchanged | abc | abc |

puts Philiprehberger::EnvDiff.to_html(diff)
# <table>
#   <tr><th>Key</th><th>Status</th><th>Source</th><th>Target</th></tr>
#   <tr><td>NEW_KEY</td><td>added</td><td></td><td>added</td></tr>
#   ...
# </table>

Compare against system ENV

diff = Philiprehberger::EnvDiff.from_system({ "APP_ENV" => "production" })
diff = Philiprehberger::EnvDiff.from_system(".env.production")

Compare .env files

diff = Philiprehberger::EnvDiff.from_env_file(".env.development", ".env.production")
puts diff.summary

Parse .env files

vars = Philiprehberger::EnvDiff::Parser.parse(<<~ENV)
  # Database config
  DATABASE_URL=postgres://localhost/dev
  export SECRET_KEY="my-secret"
  APP_NAME='my-app'
ENV
# => { "DATABASE_URL" => "postgres://localhost/dev", "SECRET_KEY" => "my-secret", "APP_NAME" => "my-app" }

API

Method / Class Description
EnvDiff.compare(source, target, case_sensitive: true) Compare two hashes and return a Diff
EnvDiff.from_hash(hash_a, hash_b, case_sensitive: true) Alias for compare
EnvDiff.from_env_file(path_a, path_b, case_sensitive: true) Parse two .env files and compare them
EnvDiff.from_system(target, case_sensitive: true) Compare current ENV against a target hash or .env file path
EnvDiff.validate(target, required:) Check that all required keys exist in target; returns { valid:, missing: }
EnvDiff.to_markdown(diff) Format a diff result as a Markdown table string
EnvDiff.to_html(diff) Format a diff result as an HTML table string
Diff#added Array of keys in target but not source
Diff#removed Array of keys in source but not target
Diff#changed Hash of keys with different values
Diff#unchanged Array of keys with identical values
Diff#changed? true if any differences exist
Diff#summary(mask: []) Human-readable multiline diff string with optional value masking
Diff#to_h Structured hash with :added, :removed, :changed, :unchanged
Diff#to_json JSON serialization of the structured hash
Diff#filter(pattern:) New Diff containing only keys matching the regex pattern
Diff#stats Hash of counts: :added, :removed, :changed, :unchanged, :total
Parser.parse(content) Parse .env string into a hash
Parser.parse_file(path:) Read and parse a .env file

Development

bundle install
bundle exec rspec
bundle exec rubocop

Support

If you find this project useful:

Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT