Permalink
Browse files

Add experimental {include:Object attribute} syntax to expand attribute

values from code objects.

Attribute can also be @tagname or @tagname[N], which would expand the Nth
tag by that name on the object. In such a case, an extra attribute can
be provided to access the tag attribute, for example, to expand the
3rd param name of an object:

  {include:#method param[2] name}

To get the value of a constant:

  {include:CONST value}

By default, the attribute used is "docstring", which expands the full
docstring of the object.
  • Loading branch information...
1 parent 53b9bdc commit eb357236f019da69a78eb7663ebe1351355bdfbb @lsegal committed Apr 28, 2012
@@ -71,7 +71,7 @@ def linkify(*args)
when /^include:(\S+)/
path = $1
if obj = YARD::Registry.resolve(object.namespace, path)
- link_include_object(obj)
+ link_include_object(obj, args[1], args[2])
else
log.warn "Cannot find object at `#{path}' for inclusion"
""
@@ -95,12 +95,51 @@ def linkify(*args)
end
end
- # Includes an object's docstring into output.
+ # Includes an object's docstring or other attribute into output.
+ # If attribute begins with "@", the attribute is looked up against
+ # the first matching tag. You can also index tags using +@tag[N]+
+ # syntax.
+ #
# @since 0.6.0
+ # @example Accessing the value of a constant
+ # @example Accessing the name of the 2nd +@param+ tag
+ # link_include_object(obj, "@param[1]", "name")
# @param [CodeObjects::Base] object the object to include
+ # @param [String] attribute the attribute on the object to access.
+ # Use "@" for a tag attribute.
+ # @param [String] the tag attribute to access (only used if attribute
+ # begins with "@")
# @return [String] the object's docstring (no tags)
- def link_include_object(object)
- object.docstring
+ def link_include_object(object, attribute = nil, tag_attribute = nil)
+ valid_attrs = %w(file line source docstring scope
+ visibility name sep group signature type path value)
+ attribute ||= "docstring"
+ tag_attribute ||= 'text'
+ data = nil
+ if attribute =~ /^@/
+ tag, index = attribute[1..-1], 0
+ if %w(text name types).include?(tag_attribute)
+ if tag =~ /^(.+)\[(\d+)\]\Z/
+ tag, index = $1, $2.to_i
+ end
+ if tag_obj = object.tags(tag)[index]
+ data = tag_obj.send(tag_attribute)
+ end
+ end
+ elsif valid_attrs.include?(attribute)
+ data = object.send(attribute)
+ end
+ if data
+ data.is_a?(Array) ? data.join(", ") : data.to_s
+ else
+ log.warn "Invalid attribute #{attribute} #{tag_attribute}" +
+ " on include:#{object}"
+ ""
+ end
+ rescue => e
+ log.error "#{e.message} while expanding {include:#{object} " +
+ "#{attribute} #{tag_attribute}}"
+ ""
end
# Include a file as a docstring in output
@@ -237,8 +237,8 @@ def link_include_file(file)
end
# (see BaseHelper#link_include_object)
- def link_include_object(obj)
- htmlify(obj.docstring)
+ def link_include_object(*args)
+ h(super)
end
# (see BaseHelper#link_object)
@@ -102,10 +102,10 @@
it "should call #link_include_object for include:ObjectName" do
obj = CodeObjects::NamespaceObject.new(:root, :Foo)
- should_receive(:link_include_object).with(obj)
- linkify 'include:Foo'
+ should_receive(:link_include_object).with(obj, "a", "b")
+ linkify 'include:Foo', 'a', 'b'
end
-
+
it "should call #link_include_file for include:file:path/to/file" do
File.should_receive(:file?).with('path/to/file').and_return(true)
File.should_receive(:read).with('path/to/file').and_return('FOO')
@@ -122,7 +122,73 @@
linkify('include:file:notexist').should == ''
end
end
-
+
+ describe '#link_include_object' do
+ before do
+ @object = mock(:object)
+ @tag = mock(:tag)
+ end
+
+ it "should allow accessing of attribute on object" do
+ @object.should_receive(:value).and_return(100)
+ link_include_object(@object, "value").should == "100"
+ end
+
+ it "should warn and not expand if attribute is not whitelisted" do
+ @object.should_not_receive(:system)
+ log.should_receive(:warn).with(/Invalid attribute system/)
+ link_include_object(@object, "system").should == ""
+ end
+
+ it "should log when exception is raised within attribute" do
+ @object.should_receive(:value).and_raise(NoMethodError)
+ log.should_receive(:error).with(/NoMethodError while expanding/)
+ link_include_object(@object, "value").should == ""
+ end
+
+ it "should allow accessing tag text via @tag" do
+ @object.should_receive(:tags).with("foo").and_return([@tag, mock, mock])
+ @tag.should_receive(:text).and_return("FOO")
+ link_include_object(@object, "@foo").should == "FOO"
+ end
+
+ it "should allow accessing attributes via @tag.attr" do
+ @object.should_receive(:tags).with("foo").and_return([@tag, mock, mock])
+ @tag.should_receive(:name).and_return("FOO")
+ link_include_object(@object, "@foo", "name").should == "FOO"
+ end
+
+ it "should warn if tag attribute is not types, name or text" do
+ @tag.should_not_receive(:system)
+ log.should_receive(:warn).with(/Invalid attribute @foo.system/)
+ link_include_object(@object, "@foo", "system").should == ""
+ end
+
+ it "should allow accessing of indexed tag data via @tag[N]" do
+ @object.should_receive(:tags).with("foo").and_return([mock, @tag, mock])
+ @tag.should_receive(:text).and_return("FOO")
+ link_include_object(@object, "@foo[1]").should == "FOO"
+ end
+
+ it "should allow accessing of attributes on indexed tag data via @tag[N]" do
+ @object.should_receive(:tags).with("foo").and_return([mock, @tag, mock])
+ @tag.should_receive(:name).and_return("FOO")
+ link_include_object(@object, "@foo[1]", "name").should == "FOO"
+ end
+
+ it "should convert Array data into String" do
+ @object.should_receive(:tags).with("foo").and_return([mock, @tag, mock])
+ @tag.should_receive(:types).and_return(["String", "Array"])
+ link_include_object(@object, "@foo[1]", "types").should == "String, Array"
+ end
+
+ it "should allow accessing of tag attributes (type, name, text)" do
+ @object.should_not_receive(:system)
+ log.should_receive(:warn).with(/Invalid attribute system/)
+ link_include_object(@object, "system").should == ""
+ end
+ end
+
describe '#format_types' do
it "should return the list of types separated by commas surrounded by brackets" do
format_types(['a', 'b', 'c']).should == '(a, b, c)'

0 comments on commit eb35723

Please sign in to comment.