yacht 0.1.2 → 0.2.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.
data/.gitignore CHANGED
@@ -1,4 +1,7 @@
1
1
  coverage
2
+ tmp
3
+ doc
4
+ .yardoc
2
5
  *.gem
3
6
  .DS_Store
4
7
  Gemfile.lock
data/.yardopts ADDED
@@ -0,0 +1,6 @@
1
+ --title "Yacht: Yet Another Configuration Helper Tool"
2
+ --no-private
3
+ lib/**/*.rb
4
+ Gemfile
5
+ features//*.feature
6
+ features//*.rb
data/Gemfile CHANGED
@@ -2,4 +2,6 @@ source :rubygems
2
2
 
3
3
  gemspec
4
4
 
5
- gem 'rake'
5
+ gem 'rake'
6
+ gem 'ruby-debug19', :platform => :ruby_19
7
+ gem 'ruby-debug', :platform => :ruby_18
data/README.rdoc CHANGED
@@ -4,6 +4,7 @@ Yacht is an application configuration gem that lets you define settings for mult
4
4
  * use of ClassyStruct for improved performance over OpenStruct
5
5
  * protection of sensitive settings by specifying a whitelist in a YAML file
6
6
  * easy override of nested keys (not pretty with YAML references)
7
+ * no need for an initializer or constant to store loaded values (just use Yacht.my_key)
7
8
 
8
9
  == Installation
9
10
 
@@ -39,8 +40,13 @@ First create two (or more) YAML files in the same directory to define your setti
39
40
 
40
41
  # config/yacht/whitelist.yml (required)
41
42
  # any keys specified here can be used as a whitelist filter:
42
- # YachtLoader.to_hash(:apply_whitelist? => true)
43
+ # Yacht::Loader.to_hash(:apply_whitelist? => true)
44
+ # or
45
+ # Yacht::Loader.to_classy_struct(:apply_whitelist? => true)
43
46
  # (by default the whitelist is ignored)
47
+ # NOTE: the whitelist is ignored when using Yacht.my_key or Yacht['my_key']
48
+ # you have to use Yacht::Loader#to_hash or
49
+ # Yacht::Loader#to_classy_struct to use the whitelist
44
50
  - super_secret_info
45
51
 
46
52
  # config/yacht/local.yml (optional)
@@ -49,28 +55,16 @@ First create two (or more) YAML files in the same directory to define your setti
49
55
  production:
50
56
  cdn_host: localhost
51
57
 
52
- === Step 2: Define a constant
58
+ === Step 2: Use Yacht.my_key or Yacht['my_key']
53
59
 
54
60
  * <b>Rails</b>:
55
- # config/initializers/01_yacht.rb
56
- # Define a constant that will store all settings
57
- # looks for the following YAML files:
58
- # * config/yacht/base.yml
59
- # * config/yacht/local.yml
60
- # * config/yacht/whitelist.yml
61
- Yacht = YachtLoader.to_classy_struct
62
61
  # now you can access any key set in your YAML files with:
63
62
  # Yacht.my_key
64
63
 
65
- * Outside of rails, you need to tell +YachtLoader+ where your YAML files are stored, and what environment you want to use.
66
- YachtLoader.dir = '/path/to/YAML/dir'
67
- YachtLoader.environment = 'my_environment'
68
- Yacht = YachtLoader.to_classy_struct
69
-
70
-
71
- == How it works
72
-
73
- Currently, the +Yacht+ gem defines a class called +YachtLoader+ that will read your YAML files and output them as a regular +Hash+ or a +ClassyStruct+ . Then you use <tt>YachtLoader#to_classy_struct</tt> (or <tt>YachtLoader#to_hash</tt>) to define a constant. You can name the constant whatever you like, but you should use +Yacht+. This is because a planned feature is to have +Yacht+ defined when the gem is required and have everything namespaced under a single +Yacht+ class.
64
+ * Outside of rails, you need to tell +Yacht+ where your YAML files are stored, and what environment you want to use.
65
+ Yacht::Loader.dir = '/path/to/YAML/dir'
66
+ Yacht::Loader.environment = 'my_environment'
67
+ Yacht.my_key
74
68
 
75
69
  == License
76
70
 
data/Rakefile CHANGED
@@ -6,4 +6,4 @@ Bundler::GemHelper.install_tasks
6
6
 
7
7
  Dir['gem_tasks/**/*.rake'].each { |rake| load rake }
8
8
 
9
- task :default => [:spec]
9
+ task :default => [:spec, :features]
@@ -0,0 +1,80 @@
1
+ Feature: Load configuration settings
2
+ In order to organize my configuration settings
3
+ As a developer using Yacht
4
+ I want to load configuration settings from an external source like a YAML file
5
+
6
+ Background:
7
+ Given a file named "base.yml" with:
8
+ """
9
+ default:
10
+ :api_key: some_fake_key
11
+ :partner_sites:
12
+ - twitter
13
+ - github
14
+ :mail:
15
+ :host: localhost
16
+ :from: Our great company
17
+ development:
18
+ :api_key: some_development_key
19
+ production:
20
+ :api_key: the_real_mccoy
21
+ :partner_sites:
22
+ - facebook
23
+ :mail:
24
+ host: example.com
25
+ reply-to: info@example.com
26
+ """
27
+
28
+ Scenario: Load from YAML
29
+ When I load Yacht with environment: "development"
30
+ Then Yacht should contain the following hash:
31
+ """
32
+ {
33
+ :api_key => 'some_development_key',
34
+ :partner_sites => [
35
+ 'twitter',
36
+ 'github'
37
+ ],
38
+ :mail => {
39
+ :host => 'localhost',
40
+ :from => 'Our great company'
41
+ }
42
+ }
43
+ """
44
+
45
+ Scenario: Local overrides with local.yml
46
+ Given a file named "local.yml" with:
47
+ """
48
+ :api_key: some_crazy_local_key
49
+ """
50
+ When I load Yacht with environment: "development"
51
+ Then Yacht should contain the following hash:
52
+ """
53
+ {
54
+ :api_key => 'some_crazy_local_key',
55
+ :partner_sites => [
56
+ 'twitter',
57
+ 'github'
58
+ ],
59
+ :mail => {
60
+ :host => 'localhost',
61
+ :from => 'Our great company'
62
+ }
63
+ }
64
+ """
65
+
66
+ Scenario: Whitelisting with whitelist.yml
67
+ Given a file named "whitelist.yml" with:
68
+ """
69
+ - :partner_sites
70
+ """
71
+ When I define the constant "MyYacht" with environment: "development" using a whitelist
72
+ Then the constant "MyYacht" should contain the following hash:
73
+ """
74
+ {
75
+ :partner_sites => [
76
+ 'twitter',
77
+ 'github'
78
+ ]
79
+ }
80
+ """
@@ -0,0 +1,30 @@
1
+ Feature: Handle missing YAML files reasonably
2
+ In order to ensure robustness and correctness of data
3
+ Missing YAML files should cause an error to be raised when reasonable
4
+
5
+ Background:
6
+ Given a file named "base.yml" with:
7
+ """
8
+ development:
9
+ api_key: some_fake_key
10
+ """
11
+
12
+ Scenario: No base.yml
13
+ Given a file named "base.yml" does not exist
14
+ When I try to use Yacht
15
+ Then Yacht should raise an error with message: "Couldn't load base config"
16
+
17
+ Scenario: No local.yml
18
+ Given a file named "local.yml" does not exist
19
+ When I try to use Yacht
20
+ Then Yacht should not raise an error
21
+
22
+ Scenario: No whitelist.yml but whitelist not used
23
+ Given a file named "whitelist.yml" does not exist
24
+ When I try to use Yacht
25
+ Then Yacht should not raise an error
26
+
27
+ Scenario: No whitelist.yml and whitelist used
28
+ Given a file named "whitelist.yml" does not exist
29
+ When I try to use Yacht with a whitelist
30
+ Then Yacht should raise an error with message: "Couldn't load whitelist"
@@ -0,0 +1,5 @@
1
+ Given /^a file named "([^"]*)" does not exist$/ do |file_name|
2
+ in_current_dir do
3
+ FileUtils.rm(file_name) if File.file?(file_name)
4
+ end
5
+ end
@@ -0,0 +1,27 @@
1
+ Then /^Yacht should raise an error with message: "([^"]*)"$/ do |message|
2
+ @last_yacht.class.should be(Yacht::LoadError)
3
+ @last_yacht.message.should == message
4
+ end
5
+
6
+ When /^I try to use Yacht( with a whitelist)?$/ do |whitelist|
7
+ @last_yacht = use_yacht(!!whitelist)
8
+ end
9
+
10
+ Then /^Yacht should not raise an error$/ do
11
+ @last_yacht.class.should_not <= Exception
12
+ end
13
+
14
+ module ErrorHandlingHelpers
15
+ # Try to use Yacht and return output of Yacht#to_classy_struct if no error raised
16
+ # If an error is raised, return it instead
17
+ def use_yacht(whitelist=false)
18
+ in_current_dir do
19
+ Yacht::Loader.dir = '.'
20
+ Yacht::Loader.environment = 'development'
21
+ Yacht::Loader.to_classy_struct(:apply_whitelist? => whitelist)
22
+ end
23
+ rescue Exception => e
24
+ e
25
+ end
26
+ end
27
+ World(ErrorHandlingHelpers)
@@ -0,0 +1,28 @@
1
+ When /^I define the constant "([^"]*)" with environment: "([^"]*)"( using a whitelist)?$/ do |constant_name, env, whitelist|
2
+ in_current_dir do
3
+ Yacht::Loader.dir = '.'
4
+ Yacht::Loader.environment = env
5
+ Object.const_set( constant_name, Yacht::Loader.to_classy_struct(:apply_whitelist? => whitelist ) )
6
+ end
7
+ end
8
+
9
+
10
+ Then /^the constant "([^"]*)" should contain the following hash:$/ do |constant_name, stringified|
11
+ hash = eval(stringified)
12
+ Object.const_get(constant_name).to_hash.should == hash # don't forget to tack on to_hash to avoid weird errors
13
+ # TODO: make ClassyStruct more Hash-like so
14
+ # i t can be compared against hashes using `==`
15
+ end
16
+
17
+ When /^I load Yacht with environment: "([^"]*)"$/ do |env|
18
+ Yacht::Loader.dir = '.'
19
+ Yacht::Loader.environment = env
20
+ end
21
+
22
+ Then /^Yacht should contain the following hash:$/ do |stringified|
23
+ hash = eval(stringified)
24
+
25
+ in_current_dir do
26
+ Yacht::Loader.to_hash.should == hash
27
+ end
28
+ end
@@ -0,0 +1,9 @@
1
+ After do
2
+ Yacht::Loader.environment = nil
3
+ Yacht::Loader.dir = nil
4
+
5
+ Yacht::Loader.instance_variable_set(:@config_file_names, nil)
6
+ Yacht::Loader.instance_variable_set(:@classy_struct_instance, nil)
7
+
8
+ Yacht.instance_variable_set(:@_loader, nil)
9
+ end
@@ -0,0 +1,4 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../../lib')
2
+
3
+ require 'aruba/cucumber'
4
+ require 'yacht'
@@ -0,0 +1,5 @@
1
+ require 'cucumber/rake/task'
2
+
3
+ Cucumber::Rake::Task.new(:features) do |t|
4
+ t.fork = false
5
+ end
data/lib/yacht/base.rb ADDED
@@ -0,0 +1,14 @@
1
+ class Yacht < BasicObject
2
+ class LoadError < ::StandardError
3
+ end
4
+
5
+ class << self
6
+ def [](key)
7
+ self._hash[key]
8
+ end
9
+
10
+ def _hash
11
+ @_hash ||= Loader.to_hash
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,32 @@
1
+ require 'classy_struct'
2
+
3
+ class Yacht < BasicObject
4
+ class Loader
5
+ class << self
6
+ def classy_struct_instance
7
+ @classy_struct_instance ||= ClassyStruct.new
8
+ end
9
+
10
+ def to_classy_struct(opts={})
11
+ classy_struct_instance.new( self.to_hash(opts) )
12
+ rescue StandardError => e
13
+ # don't do anything to our own custom errors
14
+ if e.is_a? Yacht::LoadError
15
+ raise e
16
+ else
17
+ raise Yacht::LoadError.new("Error creating ClassyStruct: #{e.message}")
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ class << self
24
+ def method_missing(method, *args, &block)
25
+ _classy_struct.send(method)
26
+ end
27
+
28
+ def _classy_struct
29
+ @_classy_struct ||= Loader.to_classy_struct
30
+ end
31
+ end
32
+ end
@@ -1,9 +1,6 @@
1
1
  # TODO: Rename YachtLoader to Yacht and somehow incorporate ClassyStruct
2
2
 
3
- class YachtLoader
4
- class LoadError < StandardError
5
- end
6
-
3
+ class Yacht::Loader
7
4
  class << self
8
5
  def environment
9
6
  @environment ||= 'default'
@@ -20,7 +17,7 @@ class YachtLoader
20
17
  end
21
18
 
22
19
  def config_file_for(config_type)
23
- raise LoadError.new "#{config_type} is not a valid config type" unless valid_config_types.include?(config_type.to_s)
20
+ raise Yacht::LoadError.new "#{config_type} is not a valid config type" unless valid_config_types.include?(config_type.to_s)
24
21
 
25
22
  full_file_path_for_config(config_type)
26
23
  end
@@ -34,7 +31,7 @@ class YachtLoader
34
31
  end
35
32
 
36
33
  def to_hash(opts={})
37
- opts[:apply_whitelist?] = false unless opts.has_key?(:apply_whitelist?)
34
+ opts[:apply_whitelist?] ||= false unless opts.has_key?(:apply_whitelist?)
38
35
  self.environment = opts[:env] if opts.has_key?(:env)
39
36
 
40
37
  if opts[:apply_whitelist?]
@@ -45,60 +42,47 @@ class YachtLoader
45
42
  end
46
43
 
47
44
  def whitelist
48
- load_config_file(:whitelist, :expect_to_load => Array) || begin
49
- raise LoadError.new("Couldn't load whitelist")
50
- end
45
+ load_config_file(:whitelist, :expect_to_load => Array) || raise( Yacht::LoadError.new("Couldn't load whitelist") )
51
46
  end
52
47
 
53
48
  def base_config
54
- load_config_file(:base) || begin
55
- raise LoadError.new("Couldn't load base config")
56
- end
49
+ load_config_file(:base) || raise( Yacht::LoadError.new("Couldn't load base config") )
57
50
  end
58
51
 
59
52
  def local_config
60
- load_config_file(:local, :require_presence? => false) || {}
53
+ load_config_file(:local) || {}
61
54
  end
62
55
 
63
56
  protected
57
+ # Wrap the YAML.load for easier mocking
58
+ def _load_config_file(file_name)
59
+ YAML.load( File.read(file_name) ) if File.exists?(file_name)
60
+ end
61
+
62
+ # Load a config file with plenty of error-checking
64
63
  def load_config_file(file_type, opts={})
65
64
  # by default, expect a Hash to be loaded
66
65
  expected_class = opts[:expect_to_load] || Hash
67
66
 
68
- # by default, raise error if file missing or empty
69
- presence_required = if opts.has_key?(:require_presence?)
70
- opts[:require_presence?]
71
- else
72
- true
73
- end
74
-
75
67
  file_name = self.config_file_for(file_type)
76
-
77
- loaded = if File.exists?(file_name)
78
- YAML.load( File.read(file_name) )
79
- else
80
- nil
81
- end
82
-
83
- # an empty YAML file will be converted to boolean false
84
- raise LoadError.new "#{file_name} cannot be empty" if presence_required && loaded === false
68
+ loaded = self._load_config_file(file_name)
85
69
 
86
70
  # YAML contained the wrong type
87
- raise LoadError.new "#{file_name} must contain #{expected_class} (got #{loaded.class})" if loaded && !loaded.is_a?(expected_class)
71
+ raise Yacht::LoadError.new "#{file_name} must contain #{expected_class} (got #{loaded.class})" if loaded && !loaded.is_a?(expected_class)
88
72
 
89
73
  loaded
90
74
  rescue => e
91
75
  # don't do anything to our own custom errors
92
- if e.is_a? YachtLoader::LoadError
76
+ if e.is_a? Yacht::LoadError
93
77
  raise e
94
78
  else
95
- # convert other errors to YachtLoader::LoadError
96
- raise LoadError.new "ERROR: loading config file: '#{file_type}': #{e}"
79
+ # convert other errors to YachtLoader::Yacht::LoadError
80
+ raise Yacht::LoadError.new "ERROR: loading config file: '#{file_type}': #{e}"
97
81
  end
98
82
  end
99
83
 
100
84
  def chain_configs(config, env)
101
- raise LoadError.new "environment '#{env}' does not exist" unless config.has_key?(env)
85
+ raise Yacht::LoadError.new "environment '#{env}' does not exist" unless config.has_key?(env)
102
86
 
103
87
  parent = if config[env]['_parent']
104
88
  chain_configs(config, config[env]['_parent'])
@@ -109,4 +93,8 @@ class YachtLoader
109
93
  parent.deep_merge(config[env])
110
94
  end
111
95
  end
112
- end
96
+
97
+ end
98
+
99
+ # Alias for Yacht::Loader for backwards compatibility
100
+ Object.const_set(:YachtLoader, Yacht::Loader)
@@ -1,4 +1,4 @@
1
- class YachtLoader
1
+ class Yacht::Loader
2
2
  class << self
3
3
  # use the current rails environment by default
4
4
  def environment
@@ -0,0 +1,3 @@
1
+ class Yacht < BasicObject
2
+ VERSION = "0.2.0"
3
+ end
data/lib/yacht.rb CHANGED
@@ -1,12 +1,7 @@
1
- # This file is named yacht.rb
2
- # so that Bundler will automatically load YachtLoader
3
- #
4
- # In the future, once we figure out how to integrate ClassyStruct,
5
- # YachtLoader will be renamed to Yacht
1
+ require "yacht/base"
2
+ require "yacht/loader"
3
+ require "yacht/classy_struct"
4
+ require "yacht/version"
6
5
 
7
- require "yacht_loader/base"
8
- require "yacht_loader/classy_struct"
9
- require "yacht_loader/version"
10
-
11
- require "yacht_loader/rails" if Object.const_defined?(:Rails)
12
- require 'monkeypatches/hash'
6
+ require "yacht/rails" if Object.const_defined?(:Rails)
7
+ require 'monkeypatches/hash'
data/spec/spec_helper.rb CHANGED
@@ -15,101 +15,8 @@ require 'yacht'
15
15
 
16
16
  RSpec.configure do |config|
17
17
  config.after :each do
18
- YachtLoader.environment = nil
19
- YachtLoader.dir = nil
20
- YachtLoader.instance_variable_set(:@config_file_names, nil)
21
- end
22
- end
23
-
24
- BASE_CONFIG_FILE = <<EOF
25
- default:
26
- name: default
27
- defaultkey: defaultvalue
28
- dog: schnauzer
29
- hashkey:
30
- foo: bar
31
- baz: wurble
32
- xyzzy: thud
33
- an_environment:
34
- name: an_environment
35
- hashkey:
36
- baz: yay
37
- dog: terrier
38
- a_child_environment:
39
- _parent: an_environment
40
- name: a_child_environment
41
- hashkey:
42
- foo: kung
43
- test:
44
- baloney: delicious
45
- EOF
46
-
47
- WHITELIST_CONFIG_FILE = <<EOF
48
- - defaultkey
49
- - hashkey
50
- EOF
51
-
52
- EMPTY_WHITELIST_CONFIG_FILE = <<EOF
53
- EOF
54
-
55
- INVALID_WHITELIST_CONFIG_FILE = <<EOF
56
- somenonsenseorother
57
- EOF
58
-
59
- LOCAL_CONFIG_FILE = <<EOF
60
- localkey: localvalue
61
- EOF
62
-
63
- EMPTY_LOCAL_CONFIG_FILE = <<EOF
64
- EOF
65
-
66
- INVALID_LOCAL_CONFIG_FILE = <<EOF
67
- someinvalidstufforother
68
- EOF
69
-
70
-
71
- # ===================================
72
- # = Helpers to mock file operations =
73
- # ===================================
74
- def banish_config_file_from_prefix(prefix)
75
- file_name = YachtLoader.config_file_for(prefix)
76
- banish_file(file_name)
77
- end
78
-
79
- # shortcut to mock config file
80
- def conjure_config_file_from_prefix(prefix, file_contents=nil)
81
- file_name = YachtLoader.config_file_for(prefix)
82
- file_contents ||= "#{prefix.upcase}_CONFIG_FILE".constantize
83
-
84
- conjure_file(file_name, file_contents)
85
- end
86
-
87
- def conjure_bad_config_file_from_prefix(prefix)
88
- file_name = "#{prefix}_config_file"
89
- file_contents = file_name.upcase.constantize
90
-
91
- conjure_file(file_name, file_contents)
92
- end
93
-
94
- # mock file existence & contents
95
- def conjure_file(file_name, file_contents)
96
- File.stub!(:exists?).with(file_name).and_return true
97
- File.stub!(:read).with(file_name).and_return file_contents
98
- end
99
-
100
- # mock file non-existence
101
- def banish_file(file_name)
102
- File.stub!(:exists?).with(file_name).and_return(false)
103
- File.stub!(:read).with(file_name).and_raise Errno::ENOENT.new("No such file or directory - #{file_name}")
104
- end
105
-
106
- # ================================================
107
- # = Railsy helper to turn strings into constants =
108
- # ================================================
109
- if !String.instance_methods.include?(:constantize)
110
- class String
111
- def constantize # NOT as awesome as ActiveSupport::Inflector#constantize
112
- Object.const_get(self)
113
- end
18
+ Yacht::Loader.environment = nil
19
+ Yacht::Loader.dir = nil
20
+ Yacht::Loader.instance_variable_set(:@config_file_names, nil)
114
21
  end
115
22
  end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Yacht do
4
+ subject { Yacht }
5
+
6
+ describe :[] do
7
+ it "should retrieve value of key from Yacht::Loader.to_hash" do
8
+ mock_hash = {}
9
+
10
+ Yacht::Loader.should_receive(:to_hash).and_return(mock_hash)
11
+ mock_hash.should_receive(:[]).with(:foo)
12
+
13
+ subject[:foo]
14
+ end
15
+ end
16
+ end