hopsoft-fig 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Hopsoft LLC
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,78 @@
1
+ =Fig
2
+
3
+ ===DRY up those magic numbers and hard coded strings into something more managable. Fig is the smart way to manage stuff that really belongs in a config file instead of scattered throughout your code.
4
+
5
+ ==Why
6
+
7
+ I know that Ruby is a dynamic language and that hard coded values aren't as big a deal, but...
8
+ it's still good practice to have a centralized place to hold certain settings.
9
+
10
+ I used to create a globally available Hash to store this stuff, but have always wanted something more powerful and elegant.
11
+
12
+
13
+ ==What
14
+
15
+ Fig is a simple to use configuration management tool for Ruby applications and libraries.
16
+
17
+ The primary features are:
18
+ * Multiple configuration files
19
+ * Dynamic updating and re-loading of configuration files
20
+ * Simple dot-notation access to configuration settings
21
+ * YAML access to configuration settings
22
+ * Safe options to request settings that may not exist
23
+ * Interpolation to help DRY up configuration files through reuse of settings
24
+ * Thread safe
25
+
26
+ ==How
27
+
28
+ The simplest way to get started is to watch the 10 minute tutorial.
29
+
30
+
31
+
32
+ ===Installation
33
+
34
+ Fig is availabe as both a Gem and as a Rails Plugin.
35
+
36
+ ====To install as a Gem:
37
+
38
+ First, be sure that Github has been added as a gem source. (This only needs to be done once.)
39
+ gem sources -a http://gems.github.com
40
+
41
+ Second, install the Gem.
42
+ sudo gem install hopsoft-fig
43
+
44
+ ====To install as a Rails Plugin:
45
+ script/plugin install git://github.com/hopsoft/fig.git
46
+
47
+
48
+ ===Usage
49
+ Create a YAML file that will serve as one of the configuration files you plan to use. In a Rails application, I usually create the file config/app.yml, but you can name the file anything you like and can save it to any location within your appliation or library.
50
+
51
+ Require Fig either explicitly or implicitly. In a Rails application, I generally do this in environment.rb.
52
+ # implicit
53
+ require 'hopsoft/fig'
54
+ # explicit
55
+ gem 'hopsoft-fig'
56
+
57
+ Instantiate a Fig object that is globally available to your application. In a Rails application, I generally do this in environment.rb.
58
+ APP_CONFIG = Fig.new(RAILS_ROOT + '/config/app.yml')
59
+
60
+ Start using your settings.
61
+ puts APP_CONFIG.settings.message
62
+ puts APP_CONFIG.yaml['message']
63
+ puts APP_CONFIG.get_setting('message')
64
+ # returns nil instead of an error when the setting doesn't exist
65
+ puts APP_CONFIG.get_setting('some.nested.setting.that.may.not.exist')
66
+
67
+ Reuse settings in your YAML file (This is a great way to apply the DRY principle to your configuration settings):
68
+ name: Nathan Hopkins
69
+ message: {fig:name} says hello.
70
+
71
+ puts APP_CONFIG.get_setting('message')
72
+ # outputs -> Nathan Hopkins says hello.
73
+
74
+ Update the YAML file and load the changes without restarting your application.
75
+ APP_CONFIG.load
76
+
77
+
78
+ Copyright (c) 2008 Hopsoft LLC, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require 'echoe'
2
+
3
+ Echoe.new('fig', '0.8.2') do |p|
4
+ p.description = "The smart way to manage configuration settings for your Ruby applications."
5
+ p.url = "http://github.com/natehop/fig"
6
+ p.author = "Nathan Hopkins, Hopsoft LLC"
7
+ p.email = "natehop@gmail.com"
8
+ p.ignore_pattern = ["nbproject*"]
9
+ p.development_dependencies = []
10
+ puts p.class
11
+ end
12
+
13
+ Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each {|file| load file}
data/fig.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{fig}
5
+ s.version = "0.8.2"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Nathan Hopkins, Hopsoft LLC"]
9
+ s.date = %q{2009-03-15}
10
+ s.description = %q{The smart way to manage configuration settings for your Ruby applications.}
11
+ s.email = %q{natehop@gmail.com}
12
+ s.extra_rdoc_files = ["README.rdoc", "lib/fig.rb", "lib/string.rb", "tasks/fig_tasks.rake"]
13
+ s.files = ["README.rdoc", "install.rb", "MIT-LICENSE", "test/test.yml", "test/test2.yml", "test/string_test.rb", "test/fig_test.rb", "Manifest", "uninstall.rb", "Rakefile", "init.rb", "lib/fig.rb", "lib/string.rb", "tasks/fig_tasks.rake", "fig.gemspec"]
14
+ s.has_rdoc = true
15
+ s.homepage = %q{http://github.com/natehop/fig}
16
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Fig", "--main", "README.rdoc"]
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = %q{fig}
19
+ s.rubygems_version = %q{1.3.1}
20
+ s.summary = %q{The smart way to manage configuration settings for your Ruby applications.}
21
+ s.test_files = ["test/string_test.rb", "test/fig_test.rb"]
22
+
23
+ if s.respond_to? :specification_version then
24
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
25
+ s.specification_version = 2
26
+
27
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
28
+ else
29
+ end
30
+ else
31
+ end
32
+ end
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'string'
2
+ require 'fig'
data/install.rb ADDED
@@ -0,0 +1 @@
1
+ # Install hook code here
data/lib/fig.rb ADDED
@@ -0,0 +1,132 @@
1
+ require 'yaml'
2
+ require 'ostruct'
3
+ require 'thread'
4
+ require File.dirname(__FILE__) + '/string'
5
+
6
+ class Fig
7
+
8
+ # Constructor...
9
+ #
10
+ # ===Params
11
+ # * *file_path* - Path to the config file that should be loaded.
12
+ def initialize(file_path)
13
+ @lock = Mutex.new
14
+ @file_path = file_path
15
+ load
16
+ end
17
+
18
+ # Returns the config file file as a YAML Hash.
19
+ def yaml
20
+ copy = {}
21
+ @lock.synchronize do
22
+ copy.merge!(@yaml)
23
+ end
24
+ copy
25
+ end
26
+
27
+
28
+ # Returns an OpenStruct object representation of the config file.
29
+ # This allows you to access config settings with dot notation.
30
+ def settings
31
+ copy = OpenStruct.new
32
+ @lock.synchronize do
33
+ copy.marshal_load(@settings.marshal_dump)
34
+ end
35
+ copy
36
+ end
37
+
38
+ # The safest way to get a config setting.
39
+ # Requesting a non-exsisting key, will simply return a nil value instead of raising an error.
40
+ #
41
+ # Examples:
42
+ # Fig.get_setting('some.nested.setting')
43
+ #
44
+ # ===Params
45
+ # * *key* - A case insensivie config key
46
+ #
47
+ # *Returns* The value of the config setting requested.
48
+ # This may be the value itself or an OpenStruct containing child args
49
+ def get_setting(key)
50
+ setting = nil
51
+
52
+ @lock.synchronize do
53
+ setting = @settings
54
+ keys = key.to_s.downcase.split(/\./)
55
+
56
+ keys.each do |k|
57
+ item = eval("setting.#{k}")
58
+ return nil unless item
59
+ setting = item
60
+ end
61
+ end
62
+
63
+ setting
64
+ end
65
+
66
+ # Loads the config file and builds the internal Fig objects.
67
+ # Can be used to reload the file when changes have been made.
68
+ def load
69
+ yaml = YAML.load_file(@file_path)
70
+ yaml.each {|k, v| interpolate_setting(yaml, v)}
71
+ settings = OpenStruct.new
72
+ add_hash(settings, yaml)
73
+
74
+ @lock.synchronize do
75
+ @yaml = yaml
76
+ @settings = settings
77
+ end
78
+ rescue
79
+ puts "Failed to load file: #{@file_path}\n#{$!}"
80
+ end
81
+
82
+ private
83
+
84
+ # Invoked recursively to implicitly interpolate all settings for the passed value.
85
+ # Config values that contain the pattern /{fig:/ are implicitly interpolated,
86
+ # replacing the "fig" placeholder with the actual value from elsewhere in the config file.
87
+ #
88
+ # Example:
89
+ # name: Nathan Hopkins
90
+ # message: "This is a test! Hello #{fig:example.name}"
91
+ #
92
+ # ===Params
93
+ # * *value* [_Object_] The value to interpolate.
94
+ def interpolate_setting(yaml, value)
95
+ if value.is_a?(Hash)
96
+ value.each {|k,v| interpolate_setting(yaml, v) }
97
+ elsif value.is_a?(String)
98
+ pattern = /\{fig:/i
99
+ start = value.index(pattern, 0)
100
+ replace = {}
101
+
102
+ while start
103
+ finish = value.index(/\}/, start)
104
+ key = value[(start + 1)..(finish - 1)]
105
+ replace[key] = eval("yaml['#{key.sub(/^fig:/i, "").gsub(/\./, "']['")}'].to_s")
106
+ start = value.index(pattern, finish)
107
+ end
108
+
109
+ value.interpolate(replace, true)
110
+ end
111
+ end
112
+
113
+ # Recursively adds a hash to an OpenStruct object, ultimately creating a complete OpenStruct object with attributes
114
+ # for all key/value pairs in the Hash.
115
+ #
116
+ # ===Params
117
+ # * *obj* - The OpenStruct object to add Hash args to.
118
+ # * *hash* - The Hash to pull args from.
119
+ def add_hash(obj, hash)
120
+ return unless hash
121
+
122
+ hash.each do |key, value|
123
+ if value.class == Hash
124
+ eval "obj.#{key} = OpenStruct.new"
125
+ add_hash(eval("obj.#{key}"), value)
126
+ else
127
+ eval "obj.#{key.downcase} = value"
128
+ end
129
+ end
130
+ end
131
+
132
+ end
data/lib/string.rb ADDED
@@ -0,0 +1,48 @@
1
+ class String
2
+
3
+ # Allows various forms of string interpolation.
4
+ # Addd to make it easier to dynamically replace YAML config values;
5
+ # howerver, this will likely come in handy elsewhere.
6
+ #
7
+ # Examples:
8
+ # "Hello! My name is ?".interpolate("Nathan Hopkins")
9
+ # "Hello! My first name is ? and my last name is ?".interpolate(["Nathan", "Hopkins"])
10
+ # 'Hello! My first name is {first_name} and my last name is {last_name}'.interpolate(:first_name => "Nathan", :last_name => "Hopkins")
11
+ #
12
+ # ===Params
13
+ # * *args* [Symbol, String, Array, Hash] The value(s) used to replace segments of the string.
14
+ # * *in_place* [Boolean] Indicates if the value should edited in place.
15
+ # Be careful when doing this, you may end up with unexpected results!
16
+ #
17
+ # ===Returns
18
+ # The new string after interpolation.
19
+ def interpolate(args, in_place=false)
20
+ args = [args] unless args.is_a?(Array) || args.is_a?(Hash)
21
+
22
+ if args.is_a?(Array)
23
+ x = -1
24
+
25
+ if in_place
26
+ self.gsub!(/\?/) do |s|
27
+ x += 1
28
+ args[x]
29
+ end
30
+ else
31
+ return self.gsub(/\?/) do |s|
32
+ x += 1
33
+ args[x]
34
+ end
35
+ end
36
+ elsif args.is_a?(Hash)
37
+ if in_place
38
+ args.each {|k, v| self.gsub!(/\{#{k.to_s}\}/i, v.to_s)}
39
+ else
40
+ new_string = String.new(self)
41
+ args.each {|k, v| new_string.gsub!(/\{#{k.to_s}\}/i, v.to_s)}
42
+ return new_string
43
+ end
44
+ end
45
+
46
+ return self
47
+ end
48
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :fig do
3
+ # # Task goes here
4
+ # end
data/test/fig_test.rb ADDED
@@ -0,0 +1,89 @@
1
+ require 'test/unit'
2
+ require 'fileutils'
3
+ require File.dirname(__FILE__) + '/../lib/fig'
4
+
5
+ class FigTest < Test::Unit::TestCase
6
+ @@fig = Fig.new(File.dirname(__FILE__) + '/test.yml')
7
+
8
+ def test_yaml_values
9
+ assert @@fig.yaml
10
+ assert @@fig.yaml['name'] == 'Nathan Hopkins'
11
+ assert @@fig.yaml['message'] == 'Hello there Nathan Hopkins'
12
+ assert @@fig.yaml['name2'] == 'Page Hopkins'
13
+ assert @@fig.yaml['message2'] == 'Hello there Page Hopkins'
14
+ assert @@fig.yaml['list'].is_a?(Array)
15
+ assert @@fig.yaml['list'].length == 6
16
+ assert @@fig.yaml['parent']['name'] == 'Nathan Hopkins'
17
+ assert @@fig.yaml['parent']['child']['name'] == 'Emma Hopkins'
18
+
19
+ assert_raise NoMethodError do
20
+ @@fig.yaml['some']['complete']['garbage']
21
+ end
22
+ end
23
+
24
+ def test_settings_values
25
+ assert @@fig.settings
26
+ assert @@fig.settings.name == 'Nathan Hopkins'
27
+ assert @@fig.settings.message == 'Hello there Nathan Hopkins'
28
+ assert @@fig.settings.name2 == 'Page Hopkins'
29
+ assert @@fig.settings.message2 == 'Hello there Page Hopkins'
30
+ assert @@fig.settings.list.is_a?(Array)
31
+ assert @@fig.settings.list.length == 6
32
+ assert @@fig.settings.parent.name == 'Nathan Hopkins'
33
+ assert @@fig.settings.parent.child.name == 'Emma Hopkins'
34
+
35
+ assert_raise NoMethodError do
36
+ @@fig.settings.some.complete.garbage
37
+ end
38
+ end
39
+
40
+ def test_get_setting
41
+ assert @@fig.get_setting('name') == 'Nathan Hopkins'
42
+ assert @@fig.get_setting('message') == 'Hello there Nathan Hopkins'
43
+ assert @@fig.get_setting('name2') == 'Page Hopkins'
44
+ assert @@fig.get_setting('message2') == 'Hello there Page Hopkins'
45
+ assert @@fig.get_setting('list').is_a?(Array)
46
+ assert @@fig.get_setting('list').length == 6
47
+ assert @@fig.get_setting('parent').name == 'Nathan Hopkins'
48
+ assert @@fig.get_setting('parent').child.name == 'Emma Hopkins'
49
+ assert @@fig.get_setting('some.complete.garbage') == nil
50
+ end
51
+
52
+ def test_change_and_reload
53
+ dir_name = File.dirname(__FILE__)
54
+ orig_file = dir_name + '/test.yml'
55
+ new_file = dir_name + '/test2.yml'
56
+ bak_file = dir_name + '/test.yml.bak'
57
+
58
+ # make a backup
59
+ FileUtils.rm(bak_file) if File.exist?(bak_file)
60
+ FileUtils.cp orig_file, bak_file
61
+
62
+ # swap files
63
+ FileUtils.mv new_file, orig_file, :force => true
64
+
65
+ # verify that the file swap didn't change anything implicitly
66
+ test_get_setting
67
+
68
+ # reload
69
+ @@fig.load
70
+
71
+ # test new settings
72
+ assert @@fig.get_setting('name') == 'Jeff Hopkins'
73
+ assert @@fig.get_setting('message') == 'Hello there Jeff Hopkins'
74
+ assert @@fig.get_setting('name2') == 'Marie Hopkins'
75
+ assert @@fig.get_setting('message2') == 'Hello there Marie Hopkins'
76
+ assert @@fig.get_setting('list').is_a?(Array)
77
+ assert @@fig.get_setting('list').length == 6
78
+ assert @@fig.get_setting('parent').name == 'Jeff Hopkins'
79
+ assert @@fig.get_setting('parent').child.name == 'Nathan Hopkins'
80
+ assert @@fig.get_setting('some.complete.garbage') == nil
81
+
82
+ # switch back
83
+ FileUtils.mv orig_file, new_file, :force => true
84
+ FileUtils.mv bak_file, orig_file, :force => true
85
+ @@fig.load
86
+ end
87
+
88
+
89
+ end
@@ -0,0 +1,54 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/../lib/string'
3
+
4
+ class StringTest < Test::Unit::TestCase
5
+
6
+ def test_interpolate_with_string
7
+ orig = "Hello! My name is ?"
8
+ interpolated = orig.interpolate("Nathan Hopkins")
9
+ expected = "Hello! My name is Nathan Hopkins"
10
+ assert_not_equal orig, interpolated, "Should not have mutated in place"
11
+ assert_equal expected, interpolated
12
+
13
+ interpolated = orig.interpolate("Nathan Hopkins", true)
14
+ assert_equal orig, interpolated, "Should have mutated in place"
15
+ assert_equal expected, interpolated
16
+ end
17
+
18
+ def test_interpolate_with_symbol
19
+ orig = "Hello! My name is ?"
20
+ interpolated = orig.interpolate(:Nathan)
21
+ expected = "Hello! My name is Nathan"
22
+ assert_not_equal orig, interpolated, "Should not have mutated in place"
23
+ assert_equal expected, interpolated
24
+
25
+ interpolated = orig.interpolate(:Nathan, true)
26
+ assert_equal orig, interpolated, "Should have mutated in place"
27
+ assert_equal expected, interpolated
28
+ end
29
+
30
+ def test_interpolate_with_array
31
+ orig = "Hello! My first name is ? and my last name is ?"
32
+ interpolated = orig.interpolate(["Nathan", :Hopkins])
33
+ expected = "Hello! My first name is Nathan and my last name is Hopkins"
34
+ assert_not_equal orig, interpolated, "Should not have mutated in place"
35
+ assert_equal expected, interpolated
36
+
37
+ interpolated = orig.interpolate(["Nathan", :Hopkins], true)
38
+ assert_equal orig, interpolated, "Should have mutated in place"
39
+ assert_equal expected, interpolated
40
+ end
41
+
42
+ def test_interpolate_with_hash
43
+ orig = 'Hello! My first name is {first_name} and my last name is {last_name}'
44
+ interpolated = orig.interpolate(:first_name => "Nathan", :last_name => :Hopkins)
45
+ expected = "Hello! My first name is Nathan and my last name is Hopkins"
46
+ assert_not_equal orig, interpolated, "Should not have mutated in place"
47
+ assert_equal expected, interpolated
48
+
49
+ interpolated = orig.interpolate({:first_name => "Nathan", :last_name => :Hopkins}, true)
50
+ assert_equal orig, interpolated, "Should have mutated in place"
51
+ assert_equal expected, interpolated
52
+ end
53
+
54
+ end
data/test/test.yml ADDED
@@ -0,0 +1,9 @@
1
+ name: Nathan Hopkins
2
+ message: Hello there {fig:name}
3
+ message2: Hello there {fig:name2}
4
+ name2: Page Hopkins
5
+ list: [a, b, c, d, e, f]
6
+ parent:
7
+ name: '{fig:name}'
8
+ child:
9
+ name: Emma Hopkins
data/test/test2.yml ADDED
@@ -0,0 +1,9 @@
1
+ name: Jeff Hopkins
2
+ message: Hello there {fig:name}
3
+ message2: Hello there {fig:name2}
4
+ name2: Marie Hopkins
5
+ list: [a, b, c, d, e, f]
6
+ parent:
7
+ name: '{fig:name}'
8
+ child:
9
+ name: Nathan Hopkins
data/uninstall.rb ADDED
@@ -0,0 +1 @@
1
+ # Uninstall hook code here
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hopsoft-fig
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.2
5
+ platform: ruby
6
+ authors:
7
+ - Nathan Hopkins, Hopsoft LLC
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-03-15 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: The smart way to manage configuration settings for your Ruby applications.
17
+ email: natehop@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ - lib/fig.rb
25
+ - lib/string.rb
26
+ - tasks/fig_tasks.rake
27
+ files:
28
+ - README.rdoc
29
+ - install.rb
30
+ - MIT-LICENSE
31
+ - test/test.yml
32
+ - test/test2.yml
33
+ - test/string_test.rb
34
+ - test/fig_test.rb
35
+ - Manifest
36
+ - uninstall.rb
37
+ - Rakefile
38
+ - init.rb
39
+ - lib/fig.rb
40
+ - lib/string.rb
41
+ - tasks/fig_tasks.rake
42
+ - fig.gemspec
43
+ has_rdoc: true
44
+ homepage: http://github.com/natehop/fig
45
+ post_install_message:
46
+ rdoc_options:
47
+ - --line-numbers
48
+ - --inline-source
49
+ - --title
50
+ - Fig
51
+ - --main
52
+ - README.rdoc
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "1.2"
66
+ version:
67
+ requirements: []
68
+
69
+ rubyforge_project: fig
70
+ rubygems_version: 1.2.0
71
+ signing_key:
72
+ specification_version: 2
73
+ summary: The smart way to manage configuration settings for your Ruby applications.
74
+ test_files:
75
+ - test/string_test.rb
76
+ - test/fig_test.rb