Browse files

cleanup & documentation

  • Loading branch information...
1 parent f6e9c1a commit 359b0bd265f8aee010a9dba8a31048af37e154ff @hollow hollow committed Apr 15, 2012
View
2 .yardopts
@@ -1 +1 @@
---no-private
+--protected --no-private
View
6 Gemfile
@@ -2,10 +2,8 @@ source :rubygems
gemspec
-gem 'rake'
-gem 'bundler'
-
-group :test do
+group :development, :test do
+ gem 'pry'
gem 'redcarpet'
gem 'reek'
gem 'rspec'
View
3 lib/madvertise-ext.rb
@@ -1,3 +0,0 @@
-require 'madvertise/ext/version'
-require 'madvertise/ext/config'
-require 'madvertise/ext/environment'
View
166 lib/madvertise/ext/config.rb
@@ -1,80 +1,148 @@
require 'yaml'
+require 'madvertise/ext/hash'
+##
+# The Configuration class provides a simple interface to configuration stored
+# inside of YAML files.
+#
+class Configuration < Section
+
+ # Create a new {Configuration} object.
+ #
+ # @param [Symbol] mode The mode to load from the configurtion file
+ # (production, development, etc)
+ def initialize(mode = :development)
+ @mode = mode
+ yield self if block_given?
+ end
+
+ # Load given mixins from +path+.
+ #
+ # @param [String] path The path to mixin files.
+ # @param [Array] mixins_to_use A list of mixins to load from +path+.
+ # @return [void]
+ def load_mixins(path, mixins_to_use)
+ mixins_to_use.map do |mixin_name|
+ File.join(path, "#{mixin_name}.yml")
+ end.each do |mixin_file|
+ mixin(mixin_file)
+ end
+ end
+end
+
+##
+# A {Configuration} consists of one or more Sections. A section is a hash-like
+# object that responds to all keys in the hash as if they were methods:
+#
+# > s = Section.from_hash({:v1 => 2, :nested => {:v2 => 1}})
+# > s.v1
+# => 2
+# > s.nested.v2
+# => 1
+#
class Section < Hash
class << self
+
+ # Create a new section from the given hash-like object.
+ #
+ # @param [Hash] hsh The hash to convert into a section.
+ # @return [Section] The new {Section} object.
def from_hash(hsh)
- new.tap do |result|
+ result = new.tap do |result|
hsh.each do |key, value|
- value = if value.is_a?(Hash)
- from_hash(value)
- elsif value.is_a?(Array)
- value.map do |item|
- from_hash(item)
- end
- else
- value
- end
+ result[key.to_sym] = from_value(value)
+ end
+ end
+ end
- result[key.to_sym] = value
+ # Convert the given value into a Section, list of Sections or the pure
+ # value. Used to recursively build the Section hash.
+ #
+ # @private
+ def from_value(value)
+ case value
+ when Hash
+ from_hash(value)
+ when Array
+ value.map do |item|
+ from_hash(item)
end
+ else
+ value
end
end
end
- def deep_merge(other_hash)
- self.merge(other_hash) do |key, oldval, newval|
- oldval = oldval.to_hash if oldval.respond_to?(:to_hash)
- newval = newval.to_hash if newval.respond_to?(:to_hash)
- oldval.is_a?(Hash) && newval.is_a?(Hash) ? oldval.deep_merge(newval) : newval
+ # Mixin a configuration snippet into the current section.
+ #
+ # @param [Hash, String] value A hash to merge into the current
+ # configuration. If a string is given a filename
+ # is assumed and the given file is expected to
+ # contain a YAML hash.
+ # @return [void]
+ def mixin(value)
+ unless value.is_a?(Hash)
+ value = Section.from_hash(YAML.load(File.read(file)))
end
- end
- def deep_merge!(other_hash)
- replace(deep_merge(other_hash))
+ self.deep_merge!(value[:default]) if value.has_key?(:default)
+ self.deep_merge!(value[:generic]) if value.has_key?(:generic)
+
+ if value.has_key?(@mode)
+ self.deep_merge!(value[@mode])
+ else
+ self.deep_merge!(value)
+ end
end
+ # Build the call chain including NilSections.
+ #
+ # @private
def method_missing(name, *args)
- if name.to_s[-1] == ?=
- self[name.to_s[0..-2].to_sym] = args.first
+ if name.to_s =~ /(.*)=$/
+ self[$1.to_sym] = Section.from_value(args.first)
else
value = self[name]
- self[name] = value.call if value.is_a?(Proc)
- self[name]
+ value = value.call if value.is_a?(Proc)
+ value = NilSection.new if value.nil?
+ self[name] = value
end
end
end
-class Configuration < Section
- def initialize(mode = :development)
- @mode = mode
- yield self if block_given?
+##
+# A NilSection is returned for all missing/empty values in the config file. This
+# allows for terse code when accessing values that have not been configured by
+# the user.
+#
+# Consider code like this:
+#
+# config.server.listen.tap do |listen|
+# open_socket(listen.host, listen.port)
+# end
+#
+# Given that your server component is optional and does not appear in the
+# configuration file at all, +config.server.listen+ will return a NilSection
+# that does not call the block given to tap _at all_.
+#
+class NilSection
+ def nil?
+ true
end
- def mixin(value)
- unless value.is_a?(Hash)
- value = Section.from_hash(YAML.load(File.read(file)))
- end
-
- if value.has_key?(:default)
- self.deep_merge!(value[:default])
- end
+ def empty?
+ true
+ end
- if value.has_key?(:generic)
- self.deep_merge!(value[:generic])
- end
+ def present?
+ false
+ end
- if value.has_key?(@mode)
- self.deep_merge!(value[@mode])
- else
- self.deep_merge!(value)
- end
+ def tap
+ self
end
- def load_mixins(path, mixins_to_use)
- mixins_to_use.map do |mixin_name|
- File.join(path, "#{mixin_name}.yml")
- end.each do |mixin_file|
- mixin(mixin_file)
- end
+ def method_missing(*args, &block)
+ self
end
end
View
23 lib/madvertise/ext/environment.rb
@@ -1,33 +1,56 @@
+##
+# A simple convenience class to support multiple environments in which a
+# program can run (e.g. development, production, etc).
+#
class Environment
attr_accessor :key
+ # Create a new Environment instance with the corresponding +key+ in the +ENV+
+ # hash.
+ #
+ # @param [String] key The key in +ENV+ to contain the current program
+ # environment.
+ #
def initialize(key=nil)
@key = key
end
+ # Retreive the current environment mode.
+ #
+ # @return [String] The current environment mode.
def mode
ENV[@key] || 'development'
end
+ # Retrieve the current environment mode and convert it to a symbol.
+ #
+ # @return [Symbol] The current environment mode.
def to_sym
mode.to_sym
end
+ # Return true if the current environment is +production+.
def prod?
to_sym == :production
end
+ # Return true if the current environment is +development+.
def dev?
to_sym == :development
end
+ # Return true if the current environment is +test+.
def test?
to_sym == :test
end
+ # Set the environment mode.
+ #
+ # @param [String] The new environment mode.
def set(value)
ENV[@key] = value.to_s
end
end
+# Global instance of {Environment}.
Env = Environment.new
View
20 lib/madvertise/ext/hash.rb
@@ -0,0 +1,20 @@
+##
+# Various Hash extensions.
+#
+class Hash
+
+ # Recursively merge +other_hash+ into +self+ and return the new hash.
+ def deep_merge(other_hash)
+ self.merge(other_hash) do |key, oldval, newval|
+ oldval = oldval.to_hash if oldval.respond_to?(:to_hash)
+ newval = newval.to_hash if newval.respond_to?(:to_hash)
+ oldval.is_a?(Hash) && newval.is_a?(Hash) ? oldval.deep_merge(newval) : newval
+ end
+ end
+
+ # Recursively merge and replace +other_hash+ into +self.
+ def deep_merge!(other_hash)
+ replace(deep_merge(other_hash))
+ end
+
+end
View
4 lib/madvertise/ext/mask.reek
@@ -1,7 +1,7 @@
---
NestedIterators:
exclude:
- - Configuration#build_config
+ - Section#from_hash
FeatureEnvy:
exclude:
- - Section#deep_merge
+ - Hash#deep_merge
View
3 madvertise-ext.gemspec
@@ -10,6 +10,9 @@ Gem::Specification.new do |gem|
gem.summary = %q{Ruby extensions}
gem.homepage = "https://github.com/madvertise/ext"
+ gem.add_development_dependency "rake"
+ gem.add_development_dependency "bundler"
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
gem.files = `git ls-files`.split("\n")
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")

0 comments on commit 359b0bd

Please sign in to comment.