nenv-0.3.0/0000755000004100000410000000000012677056115012522 5ustar www-datawww-datanenv-0.3.0/LICENSE.txt0000644000004100000410000000206012677056115014343 0ustar www-datawww-dataCopyright (c) 2014 Cezary Baginski MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. nenv-0.3.0/lib/0000755000004100000410000000000012677056115013270 5ustar www-datawww-datanenv-0.3.0/lib/nenv.rb0000644000004100000410000000076212677056115014570 0ustar www-datawww-datarequire 'nenv/version' require 'nenv/autoenvironment' require 'nenv/builder' def Nenv(namespace = nil) Nenv::AutoEnvironment.new(namespace).tap do |env| yield env if block_given? end end module Nenv class << self def respond_to?(meth) instance.respond_to?(meth) end def method_missing(meth, *args) instance.send(meth, *args) end def reset @instance = nil end def instance @instance ||= Nenv::AutoEnvironment.new end end end nenv-0.3.0/lib/nenv/0000755000004100000410000000000012677056115014236 5ustar www-datawww-datanenv-0.3.0/lib/nenv/autoenvironment.rb0000644000004100000410000000032412677056115020017 0ustar www-datawww-datarequire 'nenv/environment' module Nenv class AutoEnvironment < Nenv::Environment def method_missing(meth, *args) create_method(meth) unless respond_to?(meth) send(meth, *args) end end end nenv-0.3.0/lib/nenv/environment/0000755000004100000410000000000012677056115016602 5ustar www-datawww-datanenv-0.3.0/lib/nenv/environment/loader/0000755000004100000410000000000012677056115020050 5ustar www-datawww-datanenv-0.3.0/lib/nenv/environment/loader/default.rb0000644000004100000410000000021012677056115022012 0ustar www-datawww-datamodule Nenv class Environment module Loader::Default def self.call(raw_value) raw_value end end end end nenv-0.3.0/lib/nenv/environment/loader/predicate.rb0000644000004100000410000000055712677056115022344 0ustar www-datawww-datamodule Nenv class Environment module Loader::Predicate def self.call(raw_value) case raw_value when nil nil when '' fail ArgumentError, "Can't convert empty string into Bool" when '0', 'false', 'n', 'no', 'NO', 'FALSE' false else true end end end end end nenv-0.3.0/lib/nenv/environment/loader.rb0000644000004100000410000000060312677056115020374 0ustar www-datawww-datamodule Nenv class Environment module Loader require 'nenv/environment/loader/predicate' require 'nenv/environment/loader/default' def self.setup(meth, &callback) if callback callback else if meth.to_s.end_with? '?' Predicate else Default end end end end end end nenv-0.3.0/lib/nenv/environment/dumper.rb0000644000004100000410000000036112677056115020423 0ustar www-datawww-datamodule Nenv class Environment module Dumper require 'nenv/environment/dumper/default' def self.setup(&callback) if callback callback else Default end end end end end nenv-0.3.0/lib/nenv/environment/dumper/0000755000004100000410000000000012677056115020076 5ustar www-datawww-datanenv-0.3.0/lib/nenv/environment/dumper/default.rb0000644000004100000410000000024412677056115022047 0ustar www-datawww-datamodule Nenv class Environment module Dumper::Default def self.call(raw_value) raw_value.nil? ? nil : raw_value.to_s end end end end nenv-0.3.0/lib/nenv/version.rb0000644000004100000410000000004412677056115016246 0ustar www-datawww-datamodule Nenv VERSION = '0.3.0' end nenv-0.3.0/lib/nenv/environment.rb0000644000004100000410000000361212677056115017131 0ustar www-datawww-datarequire 'nenv/environment/dumper' require 'nenv/environment/loader' module Nenv class Environment class Error < ArgumentError end class MethodError < Error def initialize(meth) @meth = meth end end class AlreadyExistsError < MethodError def message format('Method %s already exists', @meth.inspect) end end def initialize(namespace = nil) @namespace = (namespace ? namespace.upcase : nil) end def create_method(meth, &block) self.class._create_env_accessor(singleton_class, meth, &block) end private def _sanitize(meth) meth.to_s[/^([^=?]*)[=?]?$/, 1].upcase end def _namespaced_sanitize(meth) [@namespace, _sanitize(meth)].compact.join('_') end class << self def create_method(meth, &block) _create_env_accessor(self, meth, &block) end def _create_env_accessor(klass, meth, &block) _fail_if_accessor_exists(klass, meth) if meth.to_s.end_with? '=' _create_env_writer(klass, meth, &block) else _create_env_reader(klass, meth, &block) end end private def _create_env_writer(klass, meth, &block) env_name = nil dumper = nil klass.send(:define_method, meth) do |raw_value| env_name ||= _namespaced_sanitize(meth) dumper ||= Dumper.setup(&block) ENV[env_name] = dumper.(raw_value) end end def _create_env_reader(klass, meth, &block) env_name = nil loader = nil klass.send(:define_method, meth) do env_name ||= _namespaced_sanitize(meth) loader ||= Loader.setup(meth, &block) loader.(ENV[env_name]) end end def _fail_if_accessor_exists(klass, meth) fail(AlreadyExistsError, meth) if klass.method_defined?(meth) end end end end nenv-0.3.0/lib/nenv/builder.rb0000644000004100000410000000022112677056115016204 0ustar www-datawww-datarequire 'nenv/environment' module Nenv module Builder def self.build(&block) Class.new(Nenv::Environment, &block) end end end nenv-0.3.0/nenv.gemspec0000644000004100000410000000203312677056115015033 0ustar www-datawww-data# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'nenv/version' Gem::Specification.new do |spec| spec.name = 'nenv' spec.version = Nenv::VERSION spec.authors = ['Cezary Baginski'] spec.email = ['cezary@chronomantic.net'] spec.summary = "Convenience wrapper for Ruby's ENV" spec.description = 'Using ENV is like using raw SQL statements in your code. We all know how that ends...' spec.homepage = 'https://github.com/e2/nenv' spec.license = 'MIT' spec.files = `git ls-files -z`.split("\x0").reject do |f| /^(?:\.rspec|\.rubocop.*\.yml|\.travis\.yml|Rakefile|Guardfile|Gemfile|spec\/.*)$/.match(f) end spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) } spec.test_files = [] spec.require_paths = ['lib'] spec.add_development_dependency 'bundler', '~> 1.7' spec.add_development_dependency 'rspec', '~> 3.1' spec.add_development_dependency 'rake', '~> 10.0' end nenv-0.3.0/.gitignore0000644000004100000410000000016612677056115014515 0ustar www-datawww-data/.bundle/ /.yardoc /Gemfile.lock /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ *.bundle *.so *.o *.a mkmf.log nenv-0.3.0/README.md0000644000004100000410000001741612677056115014012 0ustar www-datawww-data[![Build Status](https://travis-ci.org/e2/nenv.png?branch=master)](https://travis-ci.org/e2/nenv) [![Gem Version](http://img.shields.io/gem/v/nenv.svg)](http://badge.fury.io/rb/nenv) [![Dependency Status](https://gemnasium.com/e2/nenv.svg)](https://gemnasium.com/e2/nenv) [![Code Climate](https://codeclimate.com/github/e2/nenv/badges/gpa.svg)](https://codeclimate.com/github/e2/nenv) [![Coverage Status](https://coveralls.io/repos/e2/nenv/badge.png)](https://coveralls.io/r/e2/nenv) # Nenv Using ENV in Ruby is like using raw SQL statements - it feels wrong, because it is. If you agree, this gem is for you. ## The benefits over using ENV directly: - much friendlier stubbing in tests - you no longer have to care whether false is "0" or "false" or whatever - NO MORE ALL CAPS EVERYWHERE! - keys become methods - namespaces which can be passed around as objects - you can subclass! - you can marshal/unmarshal your own types automatically! - strict mode saves you from doing validation yourself - and there's more to come... Other benefits (and compared to other solutions): - should still work with Ruby 1.8 (in case anyone is still stuck with it) - it's designed to be as lightweight and as fast as possible compared to ENV - designed to be both hackable and convenient ## Installation Add this line to your application's Gemfile: ```ruby gem 'nenv', '~> 0.1' ``` And then execute: $ bundle Or install it yourself as: $ gem install nenv ## Examples !!! ### Automatic booleans You no longer have to care whether the value is "0" or "false" or "no" or "FALSE" or ... whatever ```ruby # Without Nenv t.verbose = (ENV['CI'] == 'true') ok = ENV['RUBYGEMS_GEMDEPS'] == "1" || ENV.key?('BUNDLE_GEMFILE') ENV['DEBUG'] = "true" ``` now becomes: ```ruby t.verbose = Nenv.ci? gemdeps = Nenv.rubygems_gemdeps? || Nenv.bundle_gemfile? Nenv.debug = true ``` ### "Namespaces" ```ruby # Without Nenv puts ENV['GIT_BROWSER'] puts ENV['GIT_PAGER'] puts ENV['GIT_EDITOR'] ``` now becomes: ```ruby git = Nenv :git puts git.browser puts git.pager puts git.editor ``` Or in block form ```ruby Nenv :git do |git| puts git.browser puts git.pager puts git.editor end ``` ### Custom type handling ```ruby # Code without Nenv paths = [ENV['GEM_HOME`]] + ENV['GEM_PATH'].split(':') enable_logging if Integer(ENV['WEB_CONCURRENCY']) > 1 mydata = YAML.load(ENV['MY_DATA']) ENV['VERBOSE'] = debug ? "1" : nil ``` can become: ```ruby # setup gem = Nenv :gem gem.instance.create_method(:path) { |p| p.split(':') } web = Nenv :web web.instance.create_method(:concurrency) { |c| Integer(c) } my = Nenv :my my.instance.create_method(:data) { |d| YAML.load(d) } Nenv.instance.create_method(:verbose=) { |v| v ? 1 : nil } # and then you can simply do: paths = [gem.home] + gem.path enable_logging if web.concurrency > 1 mydata = my.data Nenv.verbose = debug ``` ### Automatic conversion to string ```ruby ENV['RUBYGEMS_GEMDEPS'] = 1 # TypeError: no implicit conversion of Fixnum (...) ``` Nenv automatically uses `to_s`: ```ruby Nenv.rubygems_gemdeps = 1 # no problem here ``` ### Custom assignment ```ruby data = YAML.load(ENV['MY_DATA']) data[:foo] = :bar ENV['MY_DATA'] = YAML.dump(data) ``` can now become: ```ruby my = Nenv :my my.instance.create_method(:data) { |d| YAML.load(d) } my.instance.create_method(:data=) { |d| YAML.dump(d) } data = my.data data[:foo] = :bar my.data = data ``` ### Strict mode ```ruby # Without Nenv fail 'home not allowed' if ENV['HOME'] = Dir.pwd # BUG! Assignment instead of comparing! puts ENV['HOME'] # Now contains clobbered value ``` Now, clobbering can be prevented: ```ruby env = Nenv::Environment.new env.create_method(:home) fail 'home not allowed' if env.home = Dir.pwd # Fails with NoMethodError puts env.home # works ``` ### Mashup mode You can first define all the load/dump logic globally in one place ```ruby Nenv.instance.create_method(:web_concurrency) { |d| Integer(d) } Nenv.instance.create_method(:web_concurrency=) Nenv.instance.create_method(:path) { |p| Pathname(p.split(File::PATH_SEPARATOR)) } Nenv.instance.create_method(:path=) { |array| array.map(&:to_s).join(File::PATH_SEPARATOR) } # And now, anywhere in your app: Nenv.web_concurrency += 3 Nenv.path += Pathname.pwd + "foo" ``` ### Your own class (recommended version for simpler unit tests) ```ruby MyEnv = Nenv::Builder.build do create_method(:foo?) end MyEnv.new('my').foo? # same as ENV['MY_FOO'][/^(?:false|no|n|0)/i,1].nil? ``` ### Your own class (dynamic version - not recommended because harder to test) ```ruby class MyEnv < Nenv::Environment def initialize super("my") create_method(:foo?) end end MyEnv.new.foo? # same as ENV['MY_FOO'][/^(?:false|no|n|0)/i,1].nil? ``` ## NOTES Still, avoid using environment variables if you can. At least, avoid actually setting them - especially in multithreaded apps. As for Nenv, while you can access the same variable with or without namespaces, filters are tied to instances, e.g.: ```ruby Nenv.instance.create_method(:foo_bar) { |d| Integer(d) } Nenv('foo').instance.create_method(:bar) { |d| Float(d) } env = Nenv::Environment.new(:foo).tap { |e| e.create_method(:bar) } ``` all work on the same variable, but each uses a different filter for reading the value. ## Documentation / SemVer / API Any behavior not mentioned here (in this README) is subject to change. This includes module names, class names, file names, method names, etc. If you are relying on behavior not documented here, please open a ticket. ## What's wrong with ENV? Well sure, having ENV act like a Hash is much better than calling "getenv". Unfortunately, the advantages of using ENV make no sense: - it's faster but ... environment variables are rarely used thousands of times in tight loops - it's already an object ... but there's not much you can do with it (try ENV.class) - it's globally available ... but you can't isolate it in tests (you need to reset it every time) - you can use it to set variables ... but it's named like a const - it allows you to use keys regardless of case ... but by convention lowercase shouldn't be used except for local variables (which are only really used by shell scripts) - it's supposed to look ugly to discourage use ... but often your app/gem is forced to use 3rd party environment variables anyway - it's a simple Hash-like class ... but either you encapsulate it in your own classes - or all the value mapping/validation happens everywhere you want the data (yuck!) But the BIGGEST disadvantage is in specs, e.g.: ```ruby allow(ENV).to receive(:[]).with('MY_VARIABLE').and_return("foo") allow(ENV).to receive(:[]=).with('MY_VARIABLE', "foo bar") # (and if you get the above wrong, you may be debugging for a long, long time...) ``` which could instead be completely isolated as (and without side effects): ```ruby allow(env).to receive(:variable).and_return("foo") expect(env).to receive(:variable=).with("foo bar") # (with verifying doubles it's hard to get it wrong and get stuck) ``` Here's a full example: ```ruby # In your implementation MyEnv = Nenv::Builder.build do create_method(:variable) create_method(:variable=) end class Foo def foo MyEnv.new(:my).variable += "bar" end end # Stubbing the class in your specs RSpec.describe Foo do let(:env) { instance_double(MyEnv) } before { allow(MyEnv).to receive(:new).with(:my).and_return(env) } describe "#foo" do before { allow(env).to receive(:variable).and_return("foo") } it "appends a value" do expect(env).to receive(:variable=).with("foo bar") subject.foo end end end ``` ## Contributing 1. Fork it ( https://github.com/[my-github-username]/nenv/fork ) 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create a new Pull Request