lib/tap/support/constant.rb in tap-0.10.1 vs lib/tap/support/constant.rb in tap-0.11.0

- old
+ new

@@ -1,20 +1,81 @@ -require 'tap/support/constant_utils' -class String # :nodoc: - include Tap::Support::ConstantUtils -end +require 'tap/support/string_ext' module Tap module Support + + # A Constant serves as a placeholder for an actual constant, sort of like + # autoload. Use the constantize method to retrieve the actual constant; + # if it doesn't exist, constantize requires require_path and tries again. + # + # Object.const_defined?(:Net) # => false + # $".include?('net/http') # => false + # + # http = Constant.new('Net::HTTP', 'net/http') + # http.constantize # => Net::HTTP + # $".include?('net/http') # => true + # class Constant + class << self + + # Tries to find a declared constant under base with the specified + # const_name. When a constant is missing, constantize yields + # the current base and any non-existant constant names the block, + # if given, or raises a NameError. The block is expected + # to return the proper constant. + # + # module ConstName; end + # + # Constant.constantize('ConstName') # => ConstName + # Constant.constantize('Non::Existant') { ConstName } # => ConstName + # + def constantize(const_name, base=Object) # :yields: base, missing_const_names + unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ const_name + raise NameError, "#{const_name.inspect} is not a valid constant name!" + end + + constants = $1.split(/::/) + while !constants.empty? + unless const_is_defined?(base, constants[0]) + if block_given? + return yield(base, constants) + else + raise NameError.new("uninitialized constant #{const_name}", constants[0]) + end + end + base = base.const_get(constants.shift) + end + base + end + + private + + # helper method. Determines if a constant named + # name is defined in const. The implementation + # (annoyingly) has to be different for ruby 1.9 + # due to changes in the API. + case RUBY_VERSION + when /^1.9/ + def const_is_defined?(const, name) # :nodoc: + const.const_defined?(name, false) + end + else + def const_is_defined?(const, name) # :nodoc: + const.const_defined?(name) + end + end + end - # The camelized name for self. + # The constant name attr_reader :name - # The path to load to initialize the constant name. + # The path to load to initialize a missing constant attr_reader :require_path - + + # Initializes a new Constant with the specified constant + # name and require_path. The name should be a valid + # constant name. def initialize(name, require_path=nil) @name = name @require_path = require_path end @@ -46,25 +107,38 @@ # Returns the number of constants in nesting. def nesting_depth @nesting_depth ||= nesting.split(/::/).length end - # Returns the document for require_path, if set, or nil otherwise. + # Returns the Lazydoc document for require_path. def document - require_path ? Support::Lazydoc[require_path] : nil + require_path ? Lazydoc[require_path] : nil end + # True if another is a Constant with the same name + # and require_path as self. def ==(another) another.kind_of?(Constant) && another.name == self.name && another.require_path == self.require_path end - + + # Looks up and returns the constant indicated by name. + # If the constant cannot be found, the constantize + # requires require_path and tries again. + # + # Raises a NameError if the constant cannot be found. def constantize - name.try_constantize do |const_name| - require require_path - name.constantize + Constant.constantize(name) do + require require_path if require_path + Constant.constantize(name) end + end + + # Returns a string like: + # "#<Tap::Support::Constant:object_id Const::Name (require_path)>" + def inspect + "#<#{self.class}:#{object_id} #{name}#{@require_path == nil ? "" : " (#{@require_path})"}>" end end end end \ No newline at end of file