figgy 0.0.1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -4,3 +4,4 @@ Gemfile.lock
4
4
  pkg/*
5
5
  coverage
6
6
  tmp/
7
+ .yardoc/
data/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # figgy
2
+
3
+ Provides convenient access to configuration files in various formats, with
4
+ support for overriding the values based on environment, hostname, locale, or
5
+ any other arbitrary thing you happen to come up with.
6
+
7
+ ## Travis-CI Build Status
8
+ [![Build Status](https://secure.travis-ci.org/pd/figgy.png)](http://travis-ci.org/pd/figgy)
9
+
10
+ ## Documentation
11
+ [yardocs](http://rdoc.info/github/pd/figgy/master/frames)
12
+
13
+ ## Installation
14
+
15
+ Just like everything else these days. In your Gemfile:
16
+
17
+ gem 'figgy'
18
+
19
+ ## Overview
20
+
21
+ Set it up (say, in a Rails initializer):
22
+
23
+ AppConfig = Figgy.build do |config|
24
+ config.root = Rails.root.join('etc')
25
+
26
+ # config.foo is read from etc/foo.yml
27
+ config.define_overlay :default, nil
28
+
29
+ # config.foo is then updated with values from etc/production/foo.yml
30
+ config.define_overlay(:environment) { Rails.env }
31
+
32
+ # Maybe you need to load XML files?
33
+ config.define_handler 'xml' do |contents|
34
+ Hash.from_xml(contents)
35
+ end
36
+ end
37
+
38
+ Access it as a dottable, indifferent-access hash:
39
+
40
+ AppConfig.foo.some_key
41
+ AppConfig["foo"]["some_key"]
42
+ AppConfig[:foo].some_key
43
+
44
+ ## Thanks
45
+
46
+ This was written on [Enova Financial's](http://www.enovafinancial.com) dime/time.
@@ -1,8 +1,25 @@
1
1
  class Figgy
2
2
  class Configuration
3
- attr_reader :root, :overlays
4
- attr_accessor :always_reload, :preload, :freeze
3
+ # The directory in which to search for configuration files
4
+ attr_reader :root
5
5
 
6
+ # The list of defined overlays
7
+ attr_reader :overlays
8
+
9
+ # Whether to reload a configuration file each time it is accessed
10
+ attr_accessor :always_reload
11
+
12
+ # Whether to load all configuration files upon creation
13
+ # @note This does not prevent +:always_reload+ from working.
14
+ attr_accessor :preload
15
+
16
+ # Whether to freeze all loaded objects. Useful in production environments.
17
+ attr_accessor :freeze
18
+
19
+ # Constructs a new {Figgy::Configuration Figgy::Configuration} instance.
20
+ #
21
+ # By default, uses a +root+ of the current directory, and defines handlers
22
+ # for +.yml+, +.yaml+, +.yml.erb+, +.yaml.erb+, and +.json+.
6
23
  def initialize
7
24
  self.root = Dir.pwd
8
25
  @handlers = []
@@ -29,42 +46,68 @@ class Figgy
29
46
  @root = File.expand_path(path)
30
47
  end
31
48
 
49
+ # @see #always_reload=
32
50
  def always_reload?
33
51
  !!@always_reload
34
52
  end
35
53
 
54
+ # @see #preload=
36
55
  def preload?
37
56
  !!@preload
38
57
  end
39
58
 
59
+ # @see #freeze=
40
60
  def freeze?
41
61
  !!@freeze
42
62
  end
43
63
 
64
+ # Adds an overlay named +name+, found at +value+.
65
+ #
66
+ # If a block is given, yields to the block to determine +value+.
67
+ #
68
+ # @param name an internal name for the overlay
69
+ # @param value the value of the overlay
70
+ # @example An environment overlay
71
+ # config.define_overlay(:environment) { Rails.env }
44
72
  def define_overlay(name, value = nil)
45
73
  value = yield if block_given?
46
74
  @overlays << [name, value]
47
75
  end
48
76
 
77
+ # Adds an overlay using the combined values of other overlays.
78
+ #
79
+ # @example Searches for files in 'production_US'
80
+ # config.define_overlay :environment, 'production'
81
+ # config.define_overlay :country, 'US'
82
+ # config.define_combined_overlay :environment, :country
49
83
  def define_combined_overlay(*names)
50
84
  combined_name = names.join("_").to_sym
51
85
  value = names.map { |name| overlay_value(name) }.join("_")
52
86
  @overlays << [combined_name, value]
53
87
  end
54
88
 
89
+ # @return [Array<String>] the list of directories to search for config files
55
90
  def overlay_dirs
56
91
  return [@root] if @overlays.empty?
57
92
  overlay_values.map { |v| v ? File.join(@root, v) : @root }.uniq
58
93
  end
59
94
 
95
+ # Adds a new handler for files with any extension in +extensions+.
96
+ #
97
+ # @example Adding an XML handler
98
+ # config.define_handler 'xml' do |body|
99
+ # Hash.from_xml(body)
100
+ # end
60
101
  def define_handler(*extensions, &block)
61
102
  @handlers += extensions.map { |ext| [ext, block] }
62
103
  end
63
104
 
105
+ # @return [Array<String>] the list of recognized extensions
64
106
  def extensions
65
107
  @handlers.map { |ext, handler| ext }
66
108
  end
67
109
 
110
+ # @return [Proc] the handler for a given filename
68
111
  def handler_for(filename)
69
112
  match = @handlers.find { |ext, handler| filename =~ /\.#{ext}$/ }
70
113
  match && match.last
data/lib/figgy/finder.rb CHANGED
@@ -4,6 +4,18 @@ class Figgy
4
4
  @config = config
5
5
  end
6
6
 
7
+ # Searches for files defining the configuration key +name+, merging each
8
+ # instance found with the previous. In this way, the overlay configuration
9
+ # at +production/foo.yml+ can override values in +foo.yml+.
10
+ #
11
+ # If the contents of the file were a Hash, Figgy will translate it into
12
+ # a {Figgy::Hash Figgy::Hash} and perform deep-merging for all overlays. This
13
+ # allows you to override only a single key deep within the configuration, and to
14
+ # access it using dot-notation, symbol keys or string keys.
15
+ #
16
+ # @param [String] name the configuration file to load
17
+ # @return Whatever was in the config file loaded
18
+ # @raise [Figgy::FileNotFound] if no config file could be found for +name+
7
19
  def load(name)
8
20
  result = files_for(name).reduce(nil) do |result, file|
9
21
  object = @config.handler_for(file).call(File.read(file))
@@ -18,10 +30,13 @@ class Figgy
18
30
  deep_freeze(to_figgy_hash(result))
19
31
  end
20
32
 
33
+ # @param [String] name the configuration key to search for
34
+ # @return [Array<String>] the paths to all files to load for configuration key +name+
21
35
  def files_for(name)
22
36
  Dir[*file_globs(name)]
23
37
  end
24
38
 
39
+ # @return [Array<String>] the names of all unique configuration keys
25
40
  def all_key_names
26
41
  Dir[*file_globs].map { |file| File.basename(file).sub(/\..+$/, '') }.uniq
27
42
  end
data/lib/figgy/store.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  class Figgy
2
+ # The backing object for a {Figgy} instance.
2
3
  class Store
3
4
  def initialize(finder, config)
4
5
  @finder = finder
@@ -6,6 +7,10 @@ class Figgy
6
7
  @cache = {}
7
8
  end
8
9
 
10
+ # Retrieve the value for a key, expiring the cache and/or loading it
11
+ # if necessary.
12
+ #
13
+ # @raise [Figgy::FileNotFound] if no config file could be found for +name+
9
14
  def get(key)
10
15
  key = key.to_s
11
16
  @cache.delete(key) if @config.always_reload?
@@ -15,5 +20,15 @@ class Figgy
15
20
  @cache[key] = @finder.load(key)
16
21
  end
17
22
  end
23
+
24
+ # @return [Array<String>] the list of currently loaded keys
25
+ def keys
26
+ @cache.keys
27
+ end
28
+
29
+ # @return [Integer] the current size of the cache
30
+ def size
31
+ @cache.size
32
+ end
18
33
  end
19
34
  end
data/lib/figgy/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Figgy
2
- VERSION = "0.0.1"
2
+ VERSION = "0.9.0"
3
3
  end
data/lib/figgy.rb CHANGED
@@ -8,9 +8,24 @@ require "figgy/hash"
8
8
  require "figgy/finder"
9
9
  require "figgy/store"
10
10
 
11
+ # An instance of Figgy is the object used to provide access to your
12
+ # configuration files. This does very little but recognize missing
13
+ # methods and go look them up as a configuration key.
14
+ #
15
+ # To create a new instance, you probably want to use +Figgy.build+:
16
+ #
17
+ # MyConfig = Figgy.build do |config|
18
+ # config.root = '/path/to/my/configs'
19
+ # end
20
+ # MyConfig.foo.bar #=> read from /path/to/my/configs/foo.yml
21
+ #
22
+ # This should maybe be a BasicObject or similar, to provide as many
23
+ # available configuration keys as possible. Maybe.
11
24
  class Figgy
12
25
  FileNotFound = Class.new(StandardError)
13
26
 
27
+ # @yield [Figgy::Configuration] an object to set things up with
28
+ # @return [Figgy] a Figgy instance using the configuration
14
29
  def self.build(&block)
15
30
  config = Configuration.new
16
31
  block.call(config)
@@ -30,4 +45,13 @@ class Figgy
30
45
  def method_missing(m, *args, &block)
31
46
  @store.get(m)
32
47
  end
48
+
49
+ def inspect
50
+ if @store.size > 0
51
+ key_names = @store.keys.sort
52
+ "#<Figgy (#{@store.size} keys): #{key_names.join(' ')}>"
53
+ else
54
+ "#<Figgy (empty)>"
55
+ end
56
+ end
33
57
  end
data/spec/figgy_spec.rb CHANGED
@@ -14,6 +14,20 @@ describe Figgy do
14
14
  expect { test_config.values }.to raise_error(Figgy::FileNotFound)
15
15
  end
16
16
 
17
+ it "has a useful #inspect method" do
18
+ write_config 'values', 'foo: 1'
19
+ write_config 'wtf', 'bar: 2'
20
+
21
+ config = test_config
22
+ config.inspect.should == "#<Figgy (empty)>"
23
+
24
+ config.values
25
+ config.inspect.should == "#<Figgy (1 keys): values>"
26
+
27
+ config.wtf
28
+ config.inspect.should == "#<Figgy (2 keys): values wtf>"
29
+ end
30
+
17
31
  context "multiple extensions" do
18
32
  it "supports .yaml" do
19
33
  write_config 'values.yaml', 'foo: 1'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: figgy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.9.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-10-21 00:00:00.000000000 Z
12
+ date: 2011-10-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: json
16
- requirement: &70234275187800 !ruby/object:Gem::Requirement
16
+ requirement: &70227241849560 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70234275187800
24
+ version_requirements: *70227241849560
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rake
27
- requirement: &70234275187060 !ruby/object:Gem::Requirement
27
+ requirement: &70227241848860 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70234275187060
35
+ version_requirements: *70227241848860
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rspec
38
- requirement: &70234275186200 !ruby/object:Gem::Requirement
38
+ requirement: &70227241847880 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70234275186200
46
+ version_requirements: *70227241847880
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: simplecov
49
- requirement: &70234275185340 !ruby/object:Gem::Requirement
49
+ requirement: &70227241845880 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70234275185340
57
+ version_requirements: *70227241845880
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: aruba
60
- requirement: &70234275184520 !ruby/object:Gem::Requirement
60
+ requirement: &70227241842760 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '0'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *70234275184520
68
+ version_requirements: *70227241842760
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: heredoc_unindent
71
- requirement: &70234275177120 !ruby/object:Gem::Requirement
71
+ requirement: &70227241840320 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,7 +76,7 @@ dependencies:
76
76
  version: '0'
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *70234275177120
79
+ version_requirements: *70227241840320
80
80
  description: Access YAML, JSON (and ...) configuration files with ease
81
81
  email:
82
82
  - pd@krh.me
@@ -88,7 +88,7 @@ files:
88
88
  - .rvmrc
89
89
  - .travis.yml
90
90
  - Gemfile
91
- - README
91
+ - README.md
92
92
  - Rakefile
93
93
  - figgy.gemspec
94
94
  - lib/figgy.rb
@@ -126,3 +126,4 @@ summary: Configuration file reading
126
126
  test_files:
127
127
  - spec/figgy_spec.rb
128
128
  - spec/spec_helper.rb
129
+ has_rdoc:
data/README DELETED
@@ -1,21 +0,0 @@
1
- In Gemfile:
2
-
3
- gem 'figgy'
4
-
5
- Configure (say, in a Rails initializer):
6
-
7
- APP_CONFIG = Figgy.build do |config|
8
- config.root = Rails.root.join('etc')
9
-
10
- # config.foo is read from etc/foo.yml
11
- config.define_overlay :default, nil
12
-
13
- # config.foo is then updated with values from etc/production/foo.yml
14
- config.define_overlay(:environment) { Rails.env }
15
- end
16
-
17
- Access it as a dottable, indifferent-access hash:
18
-
19
- APP_CONFIG["foo"]["some_key"]
20
- APP_CONFIG[:foo].some_key
21
- APP_CONFIG.foo[:some_key]