├── .gitignore ├── .rspec ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console ├── dot_example └── setup ├── dot_example-1.0.0.gem ├── dot_example.gemspec ├── lib ├── dot_example.rb ├── dot_example │ ├── dot_env.rb │ ├── dot_env_dot_example.rb │ ├── hook.rb │ ├── post_checkout_hook.rb │ ├── pre_commit_hook.rb │ └── version.rb └── tasks │ └── dot_example.rake └── spec ├── dot_example_spec.rb ├── lib ├── dot_example │ ├── dot_env_dot_example_spec.rb │ ├── dot_env_spec.rb │ ├── post_checkout_hook_spec.rb │ └── pre_commit_hook_spec.rb └── tasks │ └── dot_example_rake_spec.rb ├── spec_helper.rb └── support └── .env.example /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | .env 11 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: ruby 3 | rvm: 4 | - 2.2.4 5 | before_install: gem install bundler -v 1.12.5 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of 4 | fostering an open and welcoming community, we pledge to respect all people who 5 | contribute through reporting issues, posting feature requests, updating 6 | documentation, submitting pull requests or patches, and other activities. 7 | 8 | We are committed to making participation in this project a harassment-free 9 | experience for everyone, regardless of level of experience, gender, gender 10 | identity and expression, sexual orientation, disability, personal appearance, 11 | body size, race, ethnicity, age, religion, or nationality. 12 | 13 | Examples of unacceptable behavior by participants include: 14 | 15 | * The use of sexualized language or imagery 16 | * Personal attacks 17 | * Trolling or insulting/derogatory comments 18 | * Public or private harassment 19 | * Publishing other's private information, such as physical or electronic 20 | addresses, without explicit permission 21 | * Other unethical or unprofessional conduct 22 | 23 | Project maintainers have the right and responsibility to remove, edit, or 24 | reject comments, commits, code, wiki edits, issues, and other contributions 25 | that are not aligned to this Code of Conduct, or to ban temporarily or 26 | permanently any contributor for other behaviors that they deem inappropriate, 27 | threatening, offensive, or harmful. 28 | 29 | By adopting this Code of Conduct, project maintainers commit themselves to 30 | fairly and consistently applying these principles to every aspect of managing 31 | this project. Project maintainers who do not follow or enforce the Code of 32 | Conduct may be permanently removed from the project team. 33 | 34 | This code of conduct applies both within project spaces and in public spaces 35 | when an individual is representing the project or its community. 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 38 | reported by contacting a project maintainer at stephen.m.cabrera@gmail.com. All 39 | complaints will be reviewed and investigated and will result in a response that 40 | is deemed necessary and appropriate to the circumstances. Maintainers are 41 | obligated to maintain confidentiality with regard to the reporter of an 42 | incident. 43 | 44 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 45 | version 1.3.0, available at 46 | [http://contributor-covenant.org/version/1/3/0/][version] 47 | 48 | [homepage]: http://contributor-covenant.org 49 | [version]: http://contributor-covenant.org/version/1/3/0/ -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in dot_example.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Stephen Mariano Cabrera 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DotExample 2 | It's become common practice to use a tool like the `dotenv` gem to manage environment variables locally, keeping all environment variables in a key value store in a dotfile (`.env`) and then leaving this file out of version control. However when working on a team it's easy to lose track of the latest variables that need to be added to `.env`. Often it isn't obvious when one is needed until a problem arises. Adding the keys to the README and/or to a `.env.example` file is one way to share the ENV variables needed with the rest of the team, but like many kinds of documentation it is easy for these files to get out of date. 3 | 4 | Enter `dot_example`, a simple tool for automatically documenting the environment variables in use across the team and alerting team members when a new variable has been added. This is accomplished through the use of a `.env.example` file and setting up git hooks. 5 | 6 | ## Installation 7 | 8 | Add this line to your application's Gemfile: 9 | 10 | ```ruby 11 | gem 'dot_example' 12 | ``` 13 | 14 | And then execute: 15 | 16 | $ bundle 17 | 18 | Or install it yourself as: 19 | 20 | $ gem install dot_example 21 | 22 | ## Quick Start 23 | Most of the time you should only need to manually run one command 24 | 25 | ``` 26 | dot_example setup 27 | ``` 28 | 29 | And you're done. That was easy! 30 | 31 | Running this setup script will do the following: 32 | 33 | 1. Create a new pre-commit git hook that will add the keys from your `.env` file to a `.env.example` file whenever you create a new commit. 34 | 2. Create a new post-checkout git hook that will check for new variables in the `.env.example` whenever you checkout a branch. 35 | 3. Create or add to a bin/setup file so that this setup script will be run automatically whenever anyone runs `./bin/setup` 36 | 37 | Now for anyone else who joins the project all they have to do is run `./bin/setup` and `dot_example` will get setup along with the rest of the app. They won't even need to know that it's there! 38 | 39 | ## Usage 40 | 41 | dot_example ships with three commands `setup`, `sync` and `check` 42 | 43 | ### Setup 44 | 45 | ``` 46 | $ dot_example setup 47 | ``` 48 | 49 | Sets up the githooks that will cause sync and check to run automatically. Assuming this works without a hitch this should keep you from having to manually run anything ever. You can even put a line in the bin/setup script to call this command: 50 | 51 | and then you won't even have to manually run `setup` just have everyone run `./bin/setup` when they start the project. In case you do need to run the commands for whatever reason you can run the other two commands are as follows: 52 | 53 | ### Sync 54 | ``` 55 | $ dot_example sync 56 | ``` 57 | sync will take the keys to the ENV variables are in your `.env` file and write them to a `.env.example` file. By keeping _this_ file in source control rather than the actual `.env` file you can make sure that other team members are always up to date on which ENV variables they are missing. 58 | 59 | ### Check 60 | 61 | ``` 62 | $ dot_example check 63 | ``` 64 | 65 | `check` will look at the ENV variables you currently have defined in your `.env` file and ensure they are up to date with the keys in the latest `.env.example`. This doesn't guarantee that their _values_ are correct but at least you'll receive a warning if you're missing keys and you'll know to ask someone for the ENV variables you're missing. 66 | 67 | After running `dot_example setup` the `check` command will be run whenever you checkout code. This could easily be changed though if you'd rather the check happen in a different common task such as running tests with rake. All you'd need to do then is add `dot_example check` to your rake task. 68 | 69 | ## Development 70 | 71 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 72 | 73 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 74 | 75 | ## Contributing 76 | 77 | Bug reports and pull requests are welcome on GitHub at https://github.com/smcabrera/dot_example. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. 78 | 79 | ## License 80 | 81 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 82 | 83 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task :default => :spec 7 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "dot_example" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | require "pry" 10 | Pry.start 11 | -------------------------------------------------------------------------------- /bin/dot_example: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'rubygems' 4 | require 'commander/import' 5 | require_relative '../lib/dot_example' 6 | 7 | program :name, 'dot_example' 8 | program :version, DotExample::VERSION 9 | program :description, DotExample::SUMMARY 10 | program :help, 'Author', 'Stephen Mariano Cabrera ' 11 | program :help, 'Version', "#{DotExample::VERSION} #{DotExample::DATE}" 12 | 13 | command :setup do |c| 14 | c.syntax = 'dot_example setup [options]' 15 | c.summary = 'Setup the githooks to automate the dot_example tasks' 16 | c.description = 'Setup the githooks to automate the dot_example tasks' 17 | #c.example 'description', 'command example' 18 | #c.option '--some-switch', 'Some switch that does something' 19 | c.action do |args, options| 20 | DotExample.add_pre_commit_hook 21 | DotExample.add_post_checkout_hook 22 | end 23 | end 24 | 25 | command :sync do |c| 26 | c.syntax = 'dot_example sync [options]' 27 | c.summary = 'Creates a new .env.example file with the keys from .env.' 28 | c.description = 'Creates a new .env.example file with the keys from .env.' 29 | #c.example 'dot_example sync', 'command example' 30 | c.option '--dot-env-path FILEPATH', String, 'path to the .env file if different than current directory' 31 | c.option '--dot-example-path FILEPATH', String, 'path to the .env.example file if different than current directory' 32 | #c.option '--suffix STRING', String, 'Adds a suffix to bar' 33 | c.action do |args, options| 34 | dot_env = DotEnv.new(options.dot_env_path) 35 | dot_env_dot_example = DotEnvDotExample.new(dot_env: dot_env, filename: options.dot_example_path) 36 | dot_env_dot_example.write! 37 | end 38 | end 39 | 40 | command :check do |c| 41 | c.syntax = 'dot_example check_vars [options]' 42 | c.summary = 'Checks ENV keys to make sure they are up to date with those found in .env.example' 43 | c.description = '' 44 | c.option '--dot-env-path FILEPATH', String, 'path to the .env file if different than current directory' 45 | c.option '--dot-example-path FILEPATH', String, 'path to the .env.example file if different than current directory' 46 | #c.example 'description', 'command example' 47 | #c.option '--some-switch', 'Some switch that does something' 48 | c.action do |args, options| 49 | dot_env = DotEnv.new(options.dot_env_path) 50 | dot_env_dot_example = DotEnvDotExample.new(dot_env: dot_env, filename: options.dot_example_path) 51 | dot_env_dot_example.print_missing_keys_message 52 | end 53 | end 54 | 55 | default_command :help 56 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | # Do any other automated setup that you need to do here 8 | -------------------------------------------------------------------------------- /dot_example-1.0.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smcabrera/dot_example/5abd99a38ea7da353a40410ffa04795010b6dfbd/dot_example-1.0.0.gem -------------------------------------------------------------------------------- /dot_example.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'dot_example/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "dot_example" 8 | spec.version = DotExample::VERSION 9 | spec.authors = ["Stephen Mariano Cabrera"] 10 | spec.email = ["stephen.m.cabrera@gmail.com"] 11 | 12 | spec.description = spec.summary = DotExample::SUMMARY 13 | spec.homepage = "https://www.github.com/smcabrera/dot_example" 14 | spec.license = "MIT" 15 | 16 | #spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 17 | spec.files = Dir["{bin,lib}/**/*", "LICENSE", "README.md"] 18 | #spec.bindir = "exe" 19 | #spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 20 | spec.executables = ['dot_example'] 21 | spec.require_paths = ["lib"] 22 | 23 | spec.add_runtime_dependency 'commander', '~> 4.4' 24 | spec.add_runtime_dependency 'dotenv', '~> 2.1', '>= 2.1.1' 25 | spec.add_runtime_dependency 'paint', '1.0.0' 26 | 27 | spec.add_development_dependency "bundler", "~> 1.12" 28 | spec.add_development_dependency "rake", "~> 10.0" 29 | spec.add_development_dependency "rspec", "~> 3.0" 30 | spec.add_development_dependency 'jazz_fingers', '~> 4.0', '>= 4.0.1' 31 | end 32 | -------------------------------------------------------------------------------- /lib/dot_example.rb: -------------------------------------------------------------------------------- 1 | require 'paint' 2 | require_relative "dot_example/version" 3 | require_relative "dot_example/dot_env" 4 | require_relative "dot_example/dot_env_dot_example" 5 | require_relative "dot_example/hook" 6 | require_relative "dot_example/pre_commit_hook" 7 | require_relative "dot_example/post_checkout_hook" 8 | 9 | module DotExample 10 | def self.add_pre_commit_hook 11 | pre_commit_hook = PreCommitHook.new 12 | pre_commit_hook.write! 13 | end 14 | 15 | def self.add_post_checkout_hook 16 | post_checkout_hook = PostCheckoutHook.new 17 | post_checkout_hook.write! 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/dot_example/dot_env.rb: -------------------------------------------------------------------------------- 1 | class DotEnv 2 | def initialize(filename = nil) 3 | @filename = filename || ".env" 4 | create_file_if_does_not_exist 5 | end 6 | 7 | attr_reader :filename 8 | 9 | def key_lines 10 | keys.map { |key| key + "=" }.join("\n") 11 | end 12 | 13 | def lint! 14 | unless line.match(/^[^=\s]*=[^=\s]*$/) 15 | # TODO: Don't know if this is the best error message 16 | raise "Invalid Environment variable defintion" 17 | end 18 | end 19 | 20 | def keys 21 | lines.map do |line| 22 | line.split("=")[0] 23 | end 24 | end 25 | 26 | def create_file_if_does_not_exist 27 | unless Dir.glob(filename).any? 28 | create_file 29 | end 30 | end 31 | 32 | def create_file 33 | %x[ touch #{filename} ] 34 | puts Paint[".env created", :green] 35 | # TODO: Add to .gitignore if it's not there already. 36 | end 37 | 38 | private 39 | 40 | def lines 41 | File.readlines(filename) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/dot_example/dot_env_dot_example.rb: -------------------------------------------------------------------------------- 1 | class DotEnvDotExample 2 | def initialize( options = {} ) 3 | @filename = options[:filename] || ".env.example" 4 | @dot_env = options[:dot_env] || DotEnv.new 5 | end 6 | 7 | attr_reader :filename, :dot_env 8 | 9 | def write! 10 | if keys_new_to_example.any? 11 | print_new_keys_message 12 | File.open(filename, "a") do |file| 13 | file.puts keys_new_to_example.map { |key| "#{key}=" } 14 | end 15 | end 16 | end 17 | 18 | def keys_new_to_example 19 | dot_env.keys.reject do |key| 20 | keys.map(&:chomp).include? key 21 | end 22 | end 23 | 24 | def print_new_keys_message 25 | puts Paint[ 26 | "New variables added to #{filename}", 27 | :green 28 | ] 29 | puts keys_new_to_example 30 | end 31 | 32 | def missing_keys 33 | keys.reject { |key| dot_env.keys.include?(key) } 34 | end 35 | 36 | def print_missing_keys_message 37 | if missing_keys.any? 38 | puts Paint[ 39 | "missing ENV variables from #{dot_env.filename}", 40 | :green 41 | ] 42 | puts missing_keys 43 | end 44 | end 45 | 46 | def pre_commit_hook 47 | # TODO: Make it possible to put .env and .env.example files somewhere else? 48 | # Maybe later if people actually want this feature 49 | [ 50 | "dot_example sync", 51 | "git add .env.example" 52 | ].join("\n") 53 | end 54 | 55 | private 56 | 57 | def keys 58 | lines.map{ |line| line.split("=")[0] } 59 | end 60 | 61 | def lines 62 | unless Dir.glob(filename).any? 63 | `touch #{filename}` 64 | end 65 | File.readlines(filename) 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /lib/dot_example/hook.rb: -------------------------------------------------------------------------------- 1 | class Hook 2 | def print_new_hook_message 3 | puts Paint["New #{type} hook created", :green] 4 | end 5 | 6 | def print_new_steps_message 7 | puts Paint[ "New steps added to #{type} hook\n#{steps}", :green] 8 | end 9 | 10 | def contents 11 | File.read(filepath) 12 | end 13 | 14 | def filepath 15 | File.join(".git", "hooks", type) 16 | end 17 | 18 | def create_file_if_does_not_exist 19 | unless Dir.glob(filepath).any? 20 | create_file 21 | end 22 | end 23 | 24 | def create_file 25 | %x[ touch #{filepath} ] 26 | %x[ chmod +x #{filepath} ] 27 | print_new_hook_message 28 | end 29 | 30 | def find_or_create 31 | end 32 | 33 | def write! 34 | unless contains_steps? 35 | File.open(filepath, "a") do |file| 36 | file.puts steps 37 | end 38 | print_new_steps_message 39 | end 40 | end 41 | 42 | def contains_steps? 43 | contents.include?(steps) 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/dot_example/post_checkout_hook.rb: -------------------------------------------------------------------------------- 1 | class PostCheckoutHook < Hook 2 | def initialize(dot_env_dot_example = nil) 3 | @dot_env_dot_example = dot_env_dot_example || DotEnvDotExample.new 4 | @type = "post-checkout" 5 | create_file_if_does_not_exist 6 | end 7 | 8 | attr_reader :dot_env_dot_example, :type 9 | 10 | def steps 11 | "dot_example check" 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/dot_example/pre_commit_hook.rb: -------------------------------------------------------------------------------- 1 | class PreCommitHook < Hook 2 | def initialize(dot_env_dot_example = nil) 3 | @dot_env_dot_example = dot_env_dot_example || DotEnvDotExample.new 4 | @type = "pre-commit" 5 | create_file_if_does_not_exist 6 | end 7 | 8 | attr_reader :dot_env_dot_example, :type 9 | 10 | # TODO: Make it possible to put .env and .env.example files somewhere else? 11 | # Maybe later if people actually want this feature 12 | def steps 13 | [ 14 | "dot_example sync", 15 | "git add .env.example" 16 | ].join("\n") 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/dot_example/version.rb: -------------------------------------------------------------------------------- 1 | module DotExample 2 | VERSION = "1.0.0" 3 | SUMMARY = %q(Keep your project's environment variables in snyc across your team.) 4 | DATE = %q{2016-9-21} 5 | end 6 | -------------------------------------------------------------------------------- /lib/tasks/dot_example.rake: -------------------------------------------------------------------------------- 1 | # TODO: Figure out whether it makes more sense to use rake tasks or just custom commands using commander. 2 | namespace :dot_example do 3 | desc "Setup the githooks to automate the dot_example tasks" 4 | task :setup => :environment do 5 | # Call other classes and do stuff 6 | end 7 | 8 | desc "Creates a new .env.example file with the keys from .env." 9 | task :sync => :environment do 10 | end 11 | 12 | desc "Checks ENV keys to make sure they are up to date with those found in .env.example" 13 | task :check => :environment do 14 | # Call other classes and do stuff 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /spec/dot_example_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DotExample do 4 | it 'has a version number' do 5 | expect(DotExample::VERSION).not_to be nil 6 | end 7 | 8 | describe '.add_pre_commit_hook' do 9 | #TODO: For when I figure out how to effectively test these 10 | end 11 | 12 | describe 'add_post_checkout_hook' do 13 | #TODO: For when I figure out how to effectively test these 14 | end 15 | end 16 | 17 | -------------------------------------------------------------------------------- /spec/lib/dot_example/dot_env_dot_example_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DotEnvDotExample do 4 | before { `touch .env.example` } 5 | after { `rm .env.example` } 6 | 7 | describe '.new' do 8 | context 'it is initialized without a filename or a dot_env' do 9 | it 'uses the defaults of ".env.example" and instantiating a DotEnv with its defaults' do 10 | dot_env_dot_example = DotEnvDotExample.new 11 | expect(dot_env_dot_example.filename).to eq ".env.example" 12 | expect(dot_env_dot_example.dot_env.filename).to eq ".env" 13 | end 14 | end 15 | 16 | context 'it is initialized with a filename and DotEnv' do 17 | it 'uses the given filename' do 18 | dot_env = DotEnv.new("spec/support/.env") 19 | dot_env_dot_example = DotEnvDotExample.new(filename: ".env.template", dot_env: dot_env) 20 | expect(dot_env_dot_example.filename).to eq ".env.template" 21 | expect(dot_env_dot_example.dot_env.filename).to eq "spec/support/.env" 22 | end 23 | end 24 | end 25 | 26 | describe 'write!' do 27 | it 'writes the key_lines from the .env file to filename' do 28 | dot_env = DotEnv.new("spec/support/.env") 29 | dot_env_dot_example = DotEnvDotExample.new( 30 | filename: "spec/support/.env.example", 31 | dot_env: dot_env 32 | ) 33 | dot_env_dot_example.write! 34 | 35 | expect( 36 | File.read(dot_env_dot_example.filename) 37 | ).to eq "SOME_API_KEY=\nSOME_API_SECRET=\n" 38 | end 39 | end 40 | 41 | describe 'missing_keys' do 42 | context '.env is not missing any keys from .env.example' do 43 | it 'returns an empty array' do 44 | allow(File).to receive(:readlines).with(".env").and_return( 45 | ["SOME_API_KEY=", "SOME_API_SECRET="] 46 | ) 47 | allow(File).to receive(:readlines).with(".env.example").and_return( 48 | ["SOME_API_KEY=", "SOME_API_SECRET="] 49 | ) 50 | 51 | dot_env_dot_example = DotEnvDotExample.new 52 | expect(dot_env_dot_example.missing_keys).to eq [] 53 | end 54 | end 55 | 56 | context '.env is missing a key from .env.example' do 57 | it 'returns those ENV variables in an array' do 58 | allow(File).to receive(:readlines).with(".env").and_return( 59 | ["SOME_API_KEY=some_value"] 60 | ) 61 | allow(File).to receive(:readlines).with(".env.example").and_return( 62 | ["SOME_API_KEY=", "SOME_API_SECRET="] 63 | ) 64 | 65 | dot_env_dot_example = DotEnvDotExample.new 66 | expect(dot_env_dot_example.missing_keys).to eq ["SOME_API_SECRET"] 67 | end 68 | end 69 | end 70 | 71 | describe '#keys_new_to_example' do 72 | context '.env contains new keys that .env.example did not have' do 73 | it 'returns the new keys' do 74 | allow(File).to receive(:readlines).with(".env").and_return( 75 | ["SOME_API_KEY=some_value", "SOME_API_SECRET=other_value"] 76 | ) 77 | allow(File).to receive(:readlines).with(".env.example").and_return( 78 | ["SOME_API_KEY="] 79 | ) 80 | dot_env_dot_example = DotEnvDotExample.new 81 | 82 | expect(dot_env_dot_example.keys_new_to_example).to eq ["SOME_API_SECRET"] 83 | end 84 | end 85 | 86 | context '.env and .env.example contain the same keys' do 87 | it 'returns an empty array' do 88 | allow(File).to receive(:readlines).with(".env").and_return( 89 | ["SOME_API_KEY=some_value", "SOME_API_SECRET=other_value"] 90 | ) 91 | allow(File).to receive(:readlines).with(".env.example").and_return( 92 | ["SOME_API_KEY=", "SOME_API_SECRET="] 93 | ) 94 | dot_env_dot_example = DotEnvDotExample.new 95 | 96 | expect(dot_env_dot_example.keys_new_to_example).to eq [] 97 | end 98 | end 99 | end 100 | 101 | describe '#pre_commit_hook' do 102 | context 'default filenames for .env and .env.example are used' do 103 | it 'uses these in the commands for the pre commit hook' do 104 | allow(File).to receive(:readlines).with(".env") 105 | allow(File).to receive(:readlines).with(".env.example") 106 | dot_env_dot_example = DotEnvDotExample.new 107 | 108 | expect(dot_env_dot_example.pre_commit_hook).to eq "dot_example sync\ngit add .env.example" 109 | end 110 | end 111 | 112 | #context 'alternative filenames for .env and .env.example are used' do 113 | #it 'uses these file locations in the commands' do 114 | #allow(File).to receive(:readlines).with(".env") 115 | #allow(File).to receive(:readlines).with(".env.template") 116 | #dot_env_dot_example = DotEnvDotExample.new 117 | #end 118 | #end 119 | end 120 | end 121 | -------------------------------------------------------------------------------- /spec/lib/dot_example/dot_env_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe DotEnv do 4 | describe '#key_lines' do 5 | it 'returns a string with each key on its own line' do 6 | dot_env_file = DotEnv.new("spec/support/.env") 7 | expect(dot_env_file.key_lines).to eq "SOME_API_KEY=\nSOME_API_SECRET=" 8 | end 9 | end 10 | 11 | # TODO: This logic has been moved to a lint! function but I'm not sure when and how to use that... 12 | # Maybe as a separate command that is called only when the .env needs to be valid? 13 | 14 | #describe '#key_lines' do 15 | #it 'raises an error if file contains a line with spaces in the ENV' do 16 | # Let me just see if I'm allowed to use that parameter in the initializer 17 | #WRONG _FORMATTING=a3429e8959\n 18 | #dot_env = DotEnv.new('spec/support/.env') 19 | #allow(File).to receive(:readlines).and_return( 20 | #["CORRECT_FORMATTING=28d1230fk\n", 21 | #"WRONG _FORMATTING=a3429e8959\n"] 22 | #) 23 | 24 | # TODO: Does it make sense to create a class to encapsulate the error? 25 | #expect{ dot_env.key_lines }.to raise_error(InvalidDotEnvError) 26 | #expect{ dot_env.key_lines }.to raise_error(RuntimeError) 27 | #end 28 | 29 | #it 'raises an error if file contains a line with multiple "="s' do 30 | #dot_env = DotEnv.new('spec/support/.env') 31 | #allow(File).to receive(:readlines).and_return( 32 | #["CORRECT_FORMATTING=28d1230fk\n", 33 | #"FORMATTING=WRONG=a3429e8959\n"] 34 | #) 35 | 36 | #expect{ dot_env.key_lines }.to raise_error(RuntimeError) 37 | #end 38 | 39 | #it 'does not returns an error if ENV variables are formatted correctly' do 40 | #dot_env = DotEnv.new('spec/support/.env') 41 | #allow(File).to receive(:readlines).and_return( 42 | #["CORRECT_FORMATTING=28d1230fk\n", 43 | #"ALSO_CORRECT=a3429e8959\n"] 44 | #) 45 | 46 | #expect{ dot_env.key_lines }.not_to raise_error 47 | #end 48 | #end 49 | end 50 | -------------------------------------------------------------------------------- /spec/lib/dot_example/post_checkout_hook_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe PostCheckoutHook do 4 | describe '#write!' do 5 | after do 6 | %x[ rm #{PostCheckoutHook.new.filepath} ] 7 | end 8 | 9 | context 'post-checkout hook already exists and contains the steps from dot_example' do 10 | before do 11 | post_checkout_hook = PostCheckoutHook.new 12 | File.open(post_checkout_hook.filepath, "w") do |file| 13 | file.puts "Existing post-checkout hook\n#{post_checkout_hook.steps}" 14 | end 15 | end 16 | 17 | it 'does not write to the file' do 18 | post_checkout_hook = PostCheckoutHook.new 19 | post_checkout_hook.write! 20 | 21 | expect(File).not_to receive(:open) 22 | end 23 | 24 | it 'file continues to have the existing post-checkout hook and the dot_example post-checkout hook' do 25 | post_checkout_hook = PostCheckoutHook.new 26 | post_checkout_hook.write! 27 | 28 | expect(File.read(post_checkout_hook.filepath)).to include(post_checkout_hook.steps) 29 | expect(File.read(post_checkout_hook.filepath)).to include("Existing post-checkout hook") 30 | end 31 | end 32 | 33 | context 'there is no existing post-checkout hook' do 34 | it 'creates a post-checkout hook and adds the steps from dot_example to it' do 35 | post_checkout_hook = PostCheckoutHook.new 36 | post_checkout_hook.write! 37 | 38 | expect(File.read(post_checkout_hook.filepath)).to include(post_checkout_hook.steps) 39 | end 40 | end 41 | 42 | context 'post-checkout hook already exists but does not contain steps from dot_example' do 43 | before do 44 | post_checkout_hook = PostCheckoutHook.new 45 | File.open(post_checkout_hook.filepath, "w") do |file| 46 | file.puts "Existing post-checkout hook" 47 | end 48 | end 49 | 50 | it 'appends the steps from dot_example to the post-checkout hook without overwriting existing hook' do 51 | post_checkout_hook = PostCheckoutHook.new 52 | post_checkout_hook.write! 53 | 54 | expect(File.read(post_checkout_hook.filepath)).to include(post_checkout_hook.steps) 55 | expect(File.read(post_checkout_hook.filepath)).to include("Existing post-checkout hook") 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/lib/dot_example/pre_commit_hook_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe PreCommitHook do 4 | describe '#write!' do 5 | after do 6 | %x[ rm #{PreCommitHook.new.filepath} ] 7 | end 8 | 9 | context 'pre commit hook already exists and contains the steps from dot_example' do 10 | before do 11 | dot_env_dot_example = DotEnvDotExample.new 12 | pre_commit_hook = PreCommitHook.new 13 | File.open(pre_commit_hook.filepath, "w") do |file| 14 | file.puts "Existing pre commit hook\n#{dot_env_dot_example.pre_commit_hook}" 15 | end 16 | end 17 | 18 | it 'does not write to the file' do 19 | pre_commit_hook = PreCommitHook.new 20 | pre_commit_hook.write! 21 | 22 | expect(File).not_to receive(:open) 23 | end 24 | 25 | it 'file continues to have the existing pre commit hook and the dot_example pre commit hook' do 26 | pre_commit_hook = PreCommitHook.new 27 | dot_env_dot_example = pre_commit_hook.dot_env_dot_example 28 | pre_commit_hook.write! 29 | 30 | expect(File.read(pre_commit_hook.filepath)).to include(dot_env_dot_example.pre_commit_hook) 31 | expect(File.read(pre_commit_hook.filepath)).to include("Existing pre commit hook") 32 | end 33 | end 34 | 35 | context 'there is no existing pre commit hook' do 36 | it 'creates a pre commit hook and adds the steps from dot_example to it' do 37 | pre_commit_hook = PreCommitHook.new 38 | dot_env_dot_example = pre_commit_hook.dot_env_dot_example 39 | pre_commit_hook.write! 40 | 41 | expect(File.read(pre_commit_hook.filepath)).to include(dot_env_dot_example.pre_commit_hook) 42 | end 43 | end 44 | 45 | context 'pre commit hook already exists but does not contain steps from dot_example' do 46 | before do 47 | pre_commit_hook = PreCommitHook.new 48 | File.open(pre_commit_hook.filepath, "w") do |file| 49 | file.puts "Existing pre commit hook" 50 | end 51 | end 52 | 53 | it 'appends the steps from dot_example to the pre commit hook without overwriting existing hook' do 54 | pre_commit_hook = PreCommitHook.new 55 | pre_commit_hook.write! 56 | 57 | expect(File.read(pre_commit_hook.filepath)).to include(pre_commit_hook.steps) 58 | expect(File.read(pre_commit_hook.filepath)).to include("Existing pre commit hook") 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/lib/tasks/dot_example_rake_spec.rb: -------------------------------------------------------------------------------- 1 | # An example rake spec borrowed from thoughtbot blog here: https://robots.thoughtbot.com/test-rake-tasks-like-a-boss 2 | # spec/lib/tasks/reports_rake_spec.rb 3 | # 4 | #describe "reports:users" do 5 | #include_context "rake" 6 | 7 | #let(:csv) { stub("csv data") } 8 | #let(:report) { stub("generated report", :to_csv => csv) } 9 | #let(:user_records) { stub("user records for report") } 10 | 11 | #before do 12 | #ReportGenerator.stubs(:generate) 13 | #UsersReport.stubs(:new => report) 14 | #User.stubs(:all => user_records) 15 | #end 16 | 17 | #its(:prerequisites) { should include("environment") } 18 | 19 | #it "generates a registrations report" do 20 | #subject.invoke 21 | #ReportGenerator.should have_received(:generate).with("users", csv) 22 | #end 23 | 24 | #it "creates the users report with the correct data" do 25 | #subject.invoke 26 | #UsersReport.should have_received(:new).with(user_records) 27 | #end 28 | #end 29 | 30 | #describe "reports:purchases" do 31 | #include_context "rake" 32 | 33 | #let(:csv) { stub("csv data") } 34 | #let(:report) { stub("generated report", :to_csv => csv) } 35 | #let(:purchase_records) { stub("purchase records for report") } 36 | 37 | #before do 38 | #ReportGenerator.stubs(:generate) 39 | #PurchasesReport.stubs(:new => report) 40 | #Purchase.stubs(:valid => purchase_records) 41 | #end 42 | 43 | #its(:prerequisites) { should include("environment") } 44 | 45 | #it "generates an purchases report" do 46 | #subject.invoke 47 | #ReportGenerator.should have_received(:generate).with("purchases", csv) 48 | #end 49 | 50 | #it "creates the purchase report with the correct data" do 51 | #subject.invoke 52 | #PurchasesReport.should have_received(:new).with(purchase_records) 53 | #end 54 | #end 55 | 56 | #describe "reports:all" do 57 | #include_context "rake" 58 | 59 | #its(:prerequisites) { should include("users") } 60 | #its(:prerequisites) { should include("purchases") } 61 | #end 62 | 63 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 2 | require 'dot_example' 3 | -------------------------------------------------------------------------------- /spec/support/.env.example: -------------------------------------------------------------------------------- 1 | SOME_API_KEY= 2 | SOME_API_SECRET= 3 | --------------------------------------------------------------------------------