figgy 0.0.1 → 0.9.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
@@ -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]