Skip to content

Commit

Permalink
Add support for @group and @endgroup preprocessed tags.
Browse files Browse the repository at this point in the history
These are not standard tags, they do not get processed alongside a code object. Instead they are lexically significant to the source file. To use a @group add

    # @group The Group Name

Anywhere in the source file before a method (and its docstring). @group and @endgroup are now reserved words that cannot be used as tags inside a docstring.

Closes gh-143
  • Loading branch information
lsegal committed Jun 17, 2010
2 parents 1851750 + 320daa0 commit cb2af34
Show file tree
Hide file tree
Showing 11 changed files with 110 additions and 6 deletions.
3 changes: 3 additions & 0 deletions lib/yard/code_objects/base.rb
Expand Up @@ -120,6 +120,9 @@ class Base
# @return [Boolean] true if the method is conditionally defined at runtime
attr_accessor :dynamic

# @return [String] the group this object is associated with
attr_accessor :group

# Is the object defined conditionally at runtime?
# @see #dynamic
def dynamic?; @dynamic end
Expand Down
4 changes: 4 additions & 0 deletions lib/yard/code_objects/namespace_object.rb
Expand Up @@ -6,6 +6,9 @@ class NamespaceObject < Base
attr_writer :constants, :cvars, :mixins, :child, :meths
attr_writer :class_attributes, :instance_attributes
attr_writer :included_constants, :included_meths

# @return [Array<String>] a list of ordered group names inside the namespace
attr_accessor :groups

# The list of objects defined in this namespace
# @return [Array<Base>] a list of objects
Expand Down Expand Up @@ -55,6 +58,7 @@ def initialize(namespace, name, *args, &block)
@instance_mixins = CodeObjectList.new(self)
@attributes = SymbolHash[:class => SymbolHash.new, :instance => SymbolHash.new]
@aliases = {}
@groups = []
super
end

Expand Down
6 changes: 6 additions & 0 deletions lib/yard/handlers/base.rb
Expand Up @@ -324,6 +324,12 @@ def register(*objects)
object.docstring = statement.comments if statement.comments
object.docstring.line_range = statement.comments_range

# Add group information
if statement.group
object.namespace.groups |= [statement.group]
object.group = statement.group
end

# Add source only to non-class non-module objects
unless object.is_a?(NamespaceObject)
object.source ||= statement
Expand Down
2 changes: 1 addition & 1 deletion lib/yard/parser/ruby/ast_node.rb
Expand Up @@ -38,7 +38,7 @@ def s(*args)
# list, like Strings or Symbols representing names. To return only
# the AstNode children of the node, use {#children}.
class AstNode < Array
attr_accessor :type, :parent, :docstring, :docstring_range, :source
attr_accessor :type, :parent, :docstring, :docstring_range, :source, :group
attr_writer :source_range, :line_range, :file, :full_source
alias comments docstring
alias comments_range docstring_range
Expand Down
2 changes: 1 addition & 1 deletion lib/yard/parser/ruby/legacy/statement.rb
Expand Up @@ -2,7 +2,7 @@ module YARD
module Parser::Ruby::Legacy
class Statement
attr_reader :tokens, :comments, :block
attr_accessor :comments_range
attr_accessor :comments_range, :group

def initialize(tokens, block = nil, comments = nil)
@tokens = tokens
Expand Down
20 changes: 20 additions & 0 deletions lib/yard/parser/ruby/legacy/statement_list.rb
Expand Up @@ -13,6 +13,7 @@ class StatementList < Array
#
# @param [TokenList, String] content the tokens to create the list from
def initialize(content)
@group = nil
if content.is_a? TokenList
@tokens = content.dup
elsif content.is_a? String
Expand Down Expand Up @@ -62,6 +63,7 @@ def next_statement
sanitize_block
@statement.pop if [TkNL, TkSPACE, TkSEMICOLON].include?(@statement.last.class)
stmt = Statement.new(@statement, @block, @comments)
stmt.group = @group
if @comments && @comments_line
stmt.comments_range = (@comments_line..(@comments_line + @comments.size - 1))
end
Expand Down Expand Up @@ -101,6 +103,23 @@ def sanitize_block
end
end
end

def preprocess_token(tk)
if tk.is_a?(TkCOMMENT)
case tk.text
when /\A# @group\s+(.+)\s*\Z/
@group = $1
true
when /\A# @endgroup\s*\Z/
@group = nil
true
else
false
end
else
false
end
end

##
# Processes a single token
Expand All @@ -110,6 +129,7 @@ def process_token(tk)
# p tk.class, tk.text, @state, @level, @current_block, "<br/>"
case @state
when :first_statement
return if preprocess_token(tk)
return if process_initial_comment(tk)
return if @statement.empty? && [TkSPACE, TkNL, TkCOMMENT].include?(tk.class)
@comments_last_line = nil
Expand Down
24 changes: 24 additions & 0 deletions lib/yard/parser/ruby/ruby_parser.rb
Expand Up @@ -30,6 +30,7 @@ def initialize(source, filename, *args)
@ns_charno = 0
@list = []
@charno = 0
@group = nil
end

def parse
Expand Down Expand Up @@ -241,10 +242,24 @@ def add_token(token, data)
undef on_embdoc
undef on_embdoc_end
undef on_parse_error
undef on_def
undef on_defs

def on_program(*args)
args.first
end

def on_def(*args)
node = AstNode.new(:def, args)
node.group = @group
visit_event(node)
end

def on_defs(*args)
node = AstNode.new(:defs, args)
node.group = @group
visit_event(node)
end

def on_body_stmt(*args)
args.compact.size == 1 ? args.first : AstNode.new(:list, args)
Expand Down Expand Up @@ -347,6 +362,15 @@ def on_label(data)
def on_comment(comment)
visit_ns_token(:comment, comment)

case comment
when /\A# @group\s+(.+)\s*\Z/
@group = $1
return
when /\A# @endgroup\s*\Z/
@group = nil
return
end

comment = comment.gsub(/^\#{1,2}\s{0,1}/, '').chomp
append_comment = @comments[lineno - 1]

Expand Down
24 changes: 24 additions & 0 deletions spec/parser/source_parser_spec.rb
Expand Up @@ -244,5 +244,29 @@ def in_order_parse(*files)
YARD::Parser::SourceParser.parse_string("$$$", :ruby)
end
end

it "should handle groups" do
Registry.clear
YARD.parse_string <<-eof
class A
# @group Group Name
def foo; end
def foo2; end
# @endgroup
def bar; end
# @group Group 2
def baz; end
end
eof

Registry.at('A').groups.should == ['Group Name', 'Group 2']
Registry.at('A#bar').group.should be_nil
Registry.at('A#foo').group.should == "Group Name"
Registry.at('A#foo2').group.should == "Group Name"
Registry.at('A#baz').group.should == "Group 2"
end
end
end
4 changes: 2 additions & 2 deletions templates/default/module/html/attribute_summary.erb
@@ -1,5 +1,5 @@
<% scopes(attr_listing) do |list, scope| %>
<h2><%= scope.to_s.capitalize %> Attribute Summary <small>(<a href="#" class="summary_toggle">collapse</a>)</small></h2>
<% groups(attr_listing, "Attribute") do |list, name| %>
<h2><%= name %> <small>(<a href="#" class="summary_toggle">collapse</a>)</small></h2>
<ul class="summary">
<% list.each do |meth| %>
<%= yieldall :item => meth %>
Expand Down
4 changes: 2 additions & 2 deletions templates/default/module/html/method_summary.erb
@@ -1,7 +1,7 @@
<% if method_listing.size > 0 %>
<% scopes(method_listing) do |list, scope| %>
<% groups(method_listing) do |list, name| %>
<h2>
<%= scope.to_s.capitalize %> Method Summary
<%= name %>
<small>(<a href="#" class="summary_toggle">collapse</a>)</small>
</h2>

Expand Down
23 changes: 23 additions & 0 deletions templates/default/module/setup.rb
Expand Up @@ -88,6 +88,29 @@ def docstring_summary(obj)
docstring_full(obj).summary
end

def groups(list, type = "Method")
if groups_data = object.groups
others = list.select {|m| !m.group }
groups_data.each do |name|
items = list.select {|m| m.group == name }
yield(items, name) unless items.empty?
end
else
others = []
group_data = {}
list.each do |meth|
if meth.group
(group_data[meth.group] ||= []) << meth
else
others << meth
end
end
group_data.each {|group, items| yield(items, group) unless items.empty? }
end

scopes(others) {|items, scope| yield(items, "#{scope.to_s.capitalize} #{type} Summary") }
end

def scopes(list)
[:class, :instance].each do |scope|
items = list.select {|m| m.scope == scope }
Expand Down

0 comments on commit cb2af34

Please sign in to comment.