lib/lazydoc/attributes.rb in lazydoc-0.9.0 vs lib/lazydoc/attributes.rb in lazydoc-1.0
- old
+ new
@@ -1,140 +1,295 @@
module Lazydoc
# Attributes adds methods to declare class-level accessors for constant
- # attributes.
+ # attributes associated with the class.
#
# # ConstName::key value
# class ConstName
# extend Lazydoc::Attributes
# lazy_attr :key
# end
#
- # ConstName.source_file # => __FILE__
- # ConstName::key.subject # => 'value'
+ # ConstName::key.subject # => 'value'
+ #
+ # Lazy attributes are inherited, but can be overridden.
+ #
+ # class SubclassA < ConstName; end
+ # SubclassA::key.subject # => 'value'
+ #
+ # # SubclassB::key overridden value
+ # class SubclassB < ConstName; end
+ # SubclassB::key.subject # => 'overridden value'
+ #
+ # You can use Attributes to register methods on modules, but currently the
+ # inheritance is a bit wonky; the accessors are methods on the extended
+ # class/module and so standard module inclusion will not pass them on.
+ # To work around you need to extend and redefine the accessors. Note,
+ # however, that methods do not need to be re-registered.
+ #
+ # module A
+ # extend Lazydoc::Attributes
+ # lazy_attr(:one, :method_one)
+ # lazy_register(:method_one)
+ #
+ # # documentation for method one
+ # def method_one; end
+ # end
#
+ # class B
+ # include A
+ # extend Lazydoc::Attributes
+ # lazy_attr(:one, :method_one)
+ # end
+ #
+ # class C < B
+ # # overriding documentation for method one
+ # def method_one; end
+ # end
+ #
+ # A::one.comment # => "documentation for method one"
+ # B::one.comment # => "documentation for method one"
+ # C::one.comment # => "overriding documentation for method one"
+ #
# ==== Keys and Register
#
- # Note that constant attributes parsed from a source file are stored in
- # const_attrs, and will ALWAYS be keyed using a string (since the
- # 'ConstName::key' syntax specifies a string key).
+ # Constant attributes parsed from a source file will ALWAYS be stored in
+ # const_attrs using a string (since the 'ConstName::key' syntax always
+ # results in a string key). A lazy_attr is basically shorthand for either
+ # of these statements:
#
- # ConstName.const_attrs['key'] # => ConstName::key
+ # ConstName.const_attrs['key'].subject # => 'value'
+ # Lazydoc::Document['ConstName']['key'].subject # => 'value'
#
- # 'Constant Attributes' specified by non-string keys are sometimes used to
- # tie comments to a constant that will NOT be resolved from the constant
- # attribute syntax. For instance you could register a method like this:
+ # By default a lazy_attr maps to the constant attribute with the same name
+ # as the accessor, but this can be overridden by specifying the string key
+ # for another attribute.
#
+ # class ConstName
+ # lazy_attr :alt, 'key'
+ # end
+ #
+ # ConstName::alt.subject # => 'value'
+ # ConstName.const_attrs['alt'] # => nil
+ #
+ # Comments specified by non-string keys may also be stored in const_attrs;
+ # these will not conflict with constant attributes parsed from a source
+ # file. For instance you could manually register a comment to a symbol key
+ # using lazy_register:
+ #
# class Sample
# extend Lazydoc::Attributes
#
- # const_attrs[:method_one] = register___
+ # lazy_register(:method_one)
+ #
# # this is the method one comment
# def method_one
# end
# end
#
- # Sample.lazydoc.resolve
- # Sample.const_attrs[:method_one].comment # => "this is the method one comment"
+ # Sample.const_attrs[:method_one].comment # => "this is the method one comment"
#
- # For easier access, you could define a lazy_attr to access the registered
- # comment. And in the simplest case, you pair a lazy_register with a
- # lazy_attr.
+ # Manually-registered comments may then be paired with a lazy_attr. As
+ # before the key for the comment is provided in the definition.
#
# class Paired
# extend Lazydoc::Attributes
#
# lazy_attr(:one, :method_one)
- # lazy_attr(:two, :method_two)
- # lazy_register(:method_two)
+ # lazy_register(:method_one)
#
- # const_attrs[:method_one] = register___
- # # this is the manually-registered method one comment
+ # # this is the method one comment
# def method_one
# end
- #
- # # this is the lazyily-registered method two comment
- # def method_two
- # end
# end
#
- # Paired.lazydoc.resolve
- # Paired.one.comment # => "this is the manually-registered method one comment"
- # Paired.two.comment # => "this is the lazyily-registered method two comment"
+ # Paired::one.comment # => "this is the method one comment"
#
+ # ==== Troubleshooting
+ #
+ # Under most circumstances Attributes will register all the necessary files
+ # to make constant attributes available. These include:
+ #
+ # * the file where the class is extended
+ # * the file where a subclass inherits from an extended class
+ # * files that declare a lazy_attr
+ #
+ # Be sure to call register_lazydoc in files that are not covered by one of
+ # these cases but nonetheless contain constant attributes that should be
+ # available to a lazy_attr.
module Attributes
-
- # The source file for the extended class. By default source_file
- # is set to the file where Attributes extends the class (if you
- # include Attributes, you must set source_file manually).
- attr_accessor :source_file
- def self.extended(base) # :nodoc:
+ # Sets source_file as the file where Attributes first extends the class.
+ def self.extended(base)
caller[1] =~ CALLER_REGEXP
- base.source_file ||= $1
- end
-
- # Inherits registered_methods from parent to child.
- def inherited(child)
- child.registered_methods.merge!(registered_methods)
+ unless base.instance_variable_defined?(:@lazydocs)
+ base.instance_variable_set(:@lazydocs, [Lazydoc[$1]])
+ end
+
+ unless base.instance_variable_defined?(:@lazy_registry)
+ base.instance_variable_set(:@lazy_registry, {})
+ end
+
super
end
- # Lazily registers the added method if marked for lazy registration.
- def method_added(sym)
- if args = registered_methods[sym]
- const_attrs[sym] ||= Lazydoc.register_caller(*args)
+ # Returns the documents registered to the extending class.
+ #
+ # By default lazydocs contains a Document for the file where Attributes
+ # extends the class, or where a subclass first inherits from an extended
+ # class (if you include Attributes, you must set lazydocs manually).
+ #
+ # Additional documents may be added by calling register_lazydoc.
+ attr_reader :lazydocs
+
+ # Returns an array of the methods whose documentation will be automatically
+ # registered by Attributes. Set as_registry to true to return a hash of
+ # of (method_name, [comment_class, caller_index]) pairs where the
+ # registration arguments are the hash values.
+ def registered_methods(as_registry=false)
+ methods = {}
+ ancestors.reverse.each do |ancestor|
+ if ancestor.kind_of?(Attributes)
+ methods.merge!(ancestor.lazy_registry)
+ end
end
- super
+ as_registry ? methods : methods.keys
end
# Returns the constant attributes resolved for the extended class.
def const_attrs
Document[to_s]
end
-
- # Returns the Document for source_file
- def lazydoc
- Lazydoc[source_file]
- end
+ protected
+
# A hash of (method_name, [comment_class, caller_index]) pairs indicating
- # methods to lazily register, and the inputs to Lazydoc.register_caller
- # used to register the method.
- def registered_methods
- @registered_methods ||= {}
+ # methods to lazily register, and the inputs used to register the method.
+ #
+ # The lazy_registry only contains methods lazily registered within the
+ # current class or module. To return methods registered throughout the
+ # inheritance hierarchy, use registered_methods(true)
+ attr_reader :lazy_registry
+
+ # Registers the calling file into lazydocs. Registration occurs by
+ # examining the call stack at the specified index.
+ def register_lazydoc(caller_index=0)
+ caller[caller_index] =~ CALLER_REGEXP
+ lazydocs << Lazydoc[File.expand_path($1)]
+ lazydocs.uniq!
+ self
end
# Creates a method that reads and resolves the constant attribute specified
- # by key, which should normally be a string (see above for more details).
- # If writable is true, a corresponding writer is also created.
+ # by key. The method has a signature like:
+ #
+ # def method(resolve=true)
+ # end
+ #
+ # To return the constant attribute without resolving, call the method with
+ # resolve == false. If writable is true, a corresponding writer is also
+ # created.
def lazy_attr(symbol, key=symbol.to_s, writable=true)
- key = case key
- when String, Symbol, Numeric, true, false, nil then key.inspect
- else "YAML.load(\'#{YAML.dump(key)}\')"
+ unless key.kind_of?(String) || key.kind_of?(Symbol)
+ raise "invalid lazy_attr key: #{key.inspect} (#{key.class})"
end
- instance_eval %Q{
-def #{symbol}(resolve=true)
- comment = const_attrs[#{key}] ||= Subject.new(nil, lazydoc)
- resolve && comment.kind_of?(Comment) ? comment.resolve : comment
-end}
-
- instance_eval(%Q{
-def #{symbol}=(comment)
- const_attrs[#{key}] = comment
-end}) if writable
+ key = key.inspect
+ instance_eval %Q{def #{symbol}(resolve=true); get_const_attr(#{key}, resolve); end}
+ instance_eval(%Q{def #{symbol}=(comment); const_attrs[#{key}] = comment; end}) if writable
+
+ register_lazydoc(1)
end
# Marks the method for lazy registration. When the method is registered,
# it will be stored in const_attrs by method_name.
def lazy_register(method_name, comment_class=Method, caller_index=1)
- registered_methods[method_name.to_sym] = [comment_class, caller_index]
+ lazy_registry[method_name.to_sym] = [comment_class, caller_index]
end
- # Registers the next comment (by default as a Method).
- def register___(comment_class=Method)
- lazydoc.register___(comment_class, 1)
+ # Manually registers the next comment into const_attrs. Note a lazy_attr
+ # will still need to be defined to access this comment as an attribute.
+ def register___(key, comment_class=Method)
+ caller[0] =~ CALLER_REGEXP
+ source_file = File.expand_path($1)
+ const_attrs[key] = Lazydoc[source_file].register___(comment_class, 1)
+ end
+
+ private
+
+ # Inherits lazy_registry from parent to child. Also registers the
+ # source_file for the child as the file where the inheritance first
+ # occurs.
+ def inherited(child)
+ unless child.instance_variable_defined?(:@lazydocs)
+ caller.each do |call|
+ next if call =~ /in `inherited'$/
+
+ call =~ CALLER_REGEXP
+ child.instance_variable_set(:@lazydocs, [Lazydoc[$1]])
+ break
+ end
+ end
+
+ unless child.instance_variable_defined?(:@lazy_registry)
+ child.instance_variable_set(:@lazy_registry, {})
+ end
+
+ super
+ end
+
+ # Helper to traverse the inheritance hierarchy. The logic of this method
+ # is described in the dsl pattern: http://gist.github.com/181961
+ def each_ancestor # :nodoc:
+ yield(self)
+
+ blank, *ancestors = self.ancestors
+ ancestors.each do |ancestor|
+ yield(ancestor) if ancestor.kind_of?(Attributes)
+ end
+
+ nil
+ end
+
+ # Lazily registers the added method if marked for lazy registration.
+ def method_added(sym)
+ current = nil
+ each_ancestor do |ancestor|
+ if ancestor.lazy_registry.has_key?(sym)
+ current = ancestor
+ break
+ end
+ end
+
+ if current
+ args = current.lazy_registry[sym]
+ const_attrs[sym] ||= Lazydoc.register_caller(*args)
+ end
+
+ super
+ end
+
+ # helper to traverse up the inheritance hierarchy looking for the first
+ # const_attr assigned to key. the lazydocs for each class will be
+ # resolved along the way, if specified.
+ def get_const_attr(key, resolve) # :nodoc:
+ each_ancestor do |ancestor|
+ const_attrs = ancestor.const_attrs
+
+ unless const_attrs.has_key?(key)
+ next unless resolve
+
+ ancestor.lazydocs.each {|doc| doc.resolve }
+ next unless const_attrs.has_key?(key)
+ end
+
+ const_attr = const_attrs[key]
+ if resolve && const_attr.kind_of?(Comment)
+ const_attr.resolve
+ end
+
+ return const_attr
+ end
end
end
end