yard 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of yard might be problematic. Click here for more details.
- data/LICENSE.txt +22 -0
- data/README.pdf +0 -0
- data/bin/yardoc +73 -0
- data/bin/yri +4 -0
- data/lib/code_object.rb +340 -0
- data/lib/formatter.rb +78 -0
- data/lib/handlers/all_handlers.rb +2 -0
- data/lib/handlers/attribute_handler.rb +46 -0
- data/lib/handlers/class_handler.rb +29 -0
- data/lib/handlers/class_variable_handler.rb +9 -0
- data/lib/handlers/code_object_handler.rb +104 -0
- data/lib/handlers/constant_handler.rb +11 -0
- data/lib/handlers/exception_handler.rb +20 -0
- data/lib/handlers/method_handler.rb +27 -0
- data/lib/handlers/mixin_handler.rb +9 -0
- data/lib/handlers/module_handler.rb +9 -0
- data/lib/handlers/visibility_handler.rb +7 -0
- data/lib/handlers/yield_handler.rb +31 -0
- data/lib/namespace.rb +98 -0
- data/lib/quick_doc.rb +104 -0
- data/lib/ruby_lex.rb +1318 -0
- data/lib/source_parser.rb +246 -0
- data/lib/tag_library.rb +162 -0
- data/lib/tag_type.rb +155 -0
- data/lib/yard.rb +3 -0
- data/templates/html_formatter.erb +455 -0
- data/test/fixtures/docstring.txt +23 -0
- data/test/fixtures/docstring2.txt +4 -0
- data/test/test_code_object.rb +66 -0
- data/test/test_namespace.rb +10 -0
- metadata +78 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
class YARD::AttributeHandler < YARD::CodeObjectHandler
|
2
|
+
handles /\Aattr(_(reader|writer|accessor))?\b/
|
3
|
+
|
4
|
+
def process
|
5
|
+
attr_type = statement.tokens.first.text.to_sym
|
6
|
+
symbols = eval("[" + statement.tokens[1..-1].to_s + "]")
|
7
|
+
read, write = true, false
|
8
|
+
|
9
|
+
# Change read/write based on attr_reader/writer/accessor
|
10
|
+
case attr_type
|
11
|
+
when :attr
|
12
|
+
# In the case of 'attr', the second parameter (if given) isn't a symbol.
|
13
|
+
read = symbols.pop if symbols.size == 2
|
14
|
+
when :attr_accessor
|
15
|
+
write = true
|
16
|
+
when :attr_reader
|
17
|
+
# change nothing
|
18
|
+
when :attr_writer
|
19
|
+
read, write = false, true
|
20
|
+
end
|
21
|
+
|
22
|
+
# Add all attributes
|
23
|
+
symbols.each do |name|
|
24
|
+
name = name.to_s
|
25
|
+
object[:attributes].update(name.to_s => { :read => read, :write => write })
|
26
|
+
|
27
|
+
# Show their methods as well
|
28
|
+
[name, "#{name}="].each do |method|
|
29
|
+
YARD::MethodObject.new(method, current_visibility, current_scope, object, statement.comments) do |obj|
|
30
|
+
if method.to_s.include? "="
|
31
|
+
src = "def #{method}(value)"
|
32
|
+
full_src = "#{src}\n @#{name} = value\nend"
|
33
|
+
doc = "Sets the attribute +#{name}+\n@param value the value to set the attribute +#{name}+ to."
|
34
|
+
else
|
35
|
+
src = "def #{method}"
|
36
|
+
full_src = "#{src}\n @#{name}\nend"
|
37
|
+
doc = "Returns the value of attribute +#{name}+"
|
38
|
+
end
|
39
|
+
obj.attach_source(src)
|
40
|
+
obj.attach_full_source(full_src)
|
41
|
+
obj.attach_docstring(doc)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class YARD::ClassHandler < YARD::CodeObjectHandler
|
2
|
+
handles RubyToken::TkCLASS
|
3
|
+
|
4
|
+
def process
|
5
|
+
words = statement.tokens.to_s.strip.split(/\s+/)
|
6
|
+
class_name, superclass = words[1], (words[3] || "Object")
|
7
|
+
if class_name == "<<"
|
8
|
+
if words[2] == "self"
|
9
|
+
class_name = nil
|
10
|
+
else
|
11
|
+
class_name = "Anonymous$#{class_name}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
if class_name.nil?
|
16
|
+
# This is a class << self block. We change the scope to class level methods
|
17
|
+
# and reset the visibility to public, but we don't enter a new namespace.
|
18
|
+
scope, vis = current_namespace.attributes[:scope], current_visibility
|
19
|
+
current_visibility = :public
|
20
|
+
current_namespace.attributes[:scope] = :class
|
21
|
+
parse_block
|
22
|
+
current_namespace.attributes[:scope], current_visibility = scope, vis
|
23
|
+
else
|
24
|
+
class_name = move_to_namespace(class_name)
|
25
|
+
class_obj = YARD::ClassObject.new(class_name, superclass, object, statement.comments)
|
26
|
+
enter_namespace(class_obj) { parse_block }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
class YARD::CodeObjectHandler
|
2
|
+
class << self
|
3
|
+
def subclasses
|
4
|
+
@@subclasses || []
|
5
|
+
end
|
6
|
+
|
7
|
+
def inherited(subclass)
|
8
|
+
@@subclasses ||= []
|
9
|
+
@@subclasses << subclass
|
10
|
+
end
|
11
|
+
|
12
|
+
def handles(token)
|
13
|
+
@handler = token
|
14
|
+
end
|
15
|
+
|
16
|
+
def handles?(tokens)
|
17
|
+
case @handler
|
18
|
+
when String
|
19
|
+
tokens.first.text == @handler
|
20
|
+
when Regexp
|
21
|
+
tokens.to_s =~ @handler
|
22
|
+
else
|
23
|
+
@handler <= tokens.first.class
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_reader :parser, :statement
|
29
|
+
|
30
|
+
def initialize(source_parser, stmt)
|
31
|
+
@parser = source_parser
|
32
|
+
@statement = stmt
|
33
|
+
end
|
34
|
+
|
35
|
+
def process
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
def current_visibility
|
40
|
+
current_namespace.attributes[:visibility]
|
41
|
+
end
|
42
|
+
|
43
|
+
def current_visibility=(value)
|
44
|
+
current_namespace.attributes[:visibility] = value
|
45
|
+
end
|
46
|
+
|
47
|
+
def current_scope
|
48
|
+
current_namespace.attributes[:scope]
|
49
|
+
end
|
50
|
+
|
51
|
+
def current_scope=(value)
|
52
|
+
current_namespace.attributes[:scope] = value
|
53
|
+
end
|
54
|
+
|
55
|
+
def object
|
56
|
+
current_namespace.object
|
57
|
+
end
|
58
|
+
|
59
|
+
def attributes
|
60
|
+
current_namespace.attributes
|
61
|
+
end
|
62
|
+
|
63
|
+
def current_namespace
|
64
|
+
parser.current_namespace
|
65
|
+
end
|
66
|
+
|
67
|
+
def current_namespace=(value)
|
68
|
+
parser.current_namespace = value
|
69
|
+
end
|
70
|
+
|
71
|
+
def enter_namespace(name, *args, &block)
|
72
|
+
namespace = parser.current_namespace
|
73
|
+
if name.is_a? YARD::CodeObject
|
74
|
+
self.current_namespace = YARD::NameStruct.new(name)
|
75
|
+
#object.add_child(name)
|
76
|
+
yield(name)
|
77
|
+
else
|
78
|
+
object.add_child(name, *args) do |obj|
|
79
|
+
self.current_namespace = YARD::NameStruct.new(obj)
|
80
|
+
obj.attach_source(statement.tokens.to_s, parser.file, statement.tokens.first.line_no)
|
81
|
+
obj.attach_docstring(statement.comments)
|
82
|
+
yield(obj)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
self.current_namespace = namespace
|
86
|
+
end
|
87
|
+
|
88
|
+
def move_to_namespace(namespace)
|
89
|
+
# If the class extends over a namespace, go to the proper object
|
90
|
+
if namespace.include? "::"
|
91
|
+
path = namespace.split("::")
|
92
|
+
name = path.pop
|
93
|
+
path = path.join("::")
|
94
|
+
full_namespace = [object.path, path].compact.join("::").gsub(/^::/, '')
|
95
|
+
current_namespace.object = YARD::Namespace.find_or_create_namespace(full_namespace)
|
96
|
+
namespace = name
|
97
|
+
end
|
98
|
+
namespace
|
99
|
+
end
|
100
|
+
|
101
|
+
def parse_block
|
102
|
+
parser.parse(statement.block) if statement.block
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class YARD::ConstantHandler < YARD::CodeObjectHandler
|
2
|
+
HANDLER_MATCH = /\A[^@\$]\S*\s*=\s*/m
|
3
|
+
handles HANDLER_MATCH
|
4
|
+
|
5
|
+
def process
|
6
|
+
return unless object.is_a? YARD::CodeObjectWithMethods
|
7
|
+
const, expr = *statement.tokens.to_s.split(/\s*=\s*/, 2)
|
8
|
+
#expr.gsub!(/\/s+/,' ')
|
9
|
+
obj = YARD::ConstantObject.new(const, object, statement)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class YARD::ExceptionHandler < YARD::CodeObjectHandler
|
2
|
+
handles 'raise'
|
3
|
+
|
4
|
+
def process
|
5
|
+
tokens = statement.tokens.reject {|tk| [RubyToken::TkSPACE, RubyToken::TkLPAREN].include? tk }
|
6
|
+
from = tokens.each_with_index do |token, index|
|
7
|
+
break index if token.class == RubyToken::TkIDENTIFIER && token.text == 'raise'
|
8
|
+
end
|
9
|
+
if from.is_a? Fixnum
|
10
|
+
exception_class = tokens[(from+1)..-1].to_s[/^\s*(\S+?),?/, 1]
|
11
|
+
# RuntimeError for Strings or no parameter
|
12
|
+
exception_class = "RuntimeError" if exception_class =~ /^\"/ || exception_class.nil?
|
13
|
+
|
14
|
+
# Only add the tag if it hasn't already been added (by exception class).
|
15
|
+
unless object.tags("raise").any? {|tag| tag.name == exception_class }
|
16
|
+
object.tags << YARD::TagLibrary.raise_tag(exception_class)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class YARD::MethodHandler < YARD::CodeObjectHandler
|
2
|
+
handles RubyToken::TkDEF
|
3
|
+
|
4
|
+
def process
|
5
|
+
stmt_nospace = statement.tokens.reject {|t| t.is_a? RubyToken::TkSPACE }
|
6
|
+
method_name, method_scope = stmt_nospace[1].text, current_scope
|
7
|
+
holding_object = object
|
8
|
+
|
9
|
+
# Use the third token (after the period) if statement begins with a "Constant." or "self."
|
10
|
+
if [RubyToken::TkCOLON2,RubyToken::TkDOT].include?(stmt_nospace[2].class)
|
11
|
+
method_class = stmt_nospace[1].text
|
12
|
+
holding_object = YARD::Namespace.find_from_path(object.path, method_class)
|
13
|
+
holding_object = YARD::Namespace.find_or_create_namespace(method_class) if holding_object.nil?
|
14
|
+
method_name = stmt_nospace[3..-1].to_s[/\A(.+?)(?:\(|;|$)/,1]
|
15
|
+
method_scope = :class
|
16
|
+
end
|
17
|
+
|
18
|
+
method_object = YARD::MethodObject.new(method_name, current_visibility,
|
19
|
+
method_scope, holding_object, statement.comments)
|
20
|
+
enter_namespace(method_object) do |obj|
|
21
|
+
#puts "->\tMethod #{obj.path} (visibility: #{obj.visibility})"
|
22
|
+
# Attach better source code
|
23
|
+
obj.attach_source(statement, parser.file)
|
24
|
+
parse_block
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class YARD::ModuleHandler < YARD::CodeObjectHandler
|
2
|
+
handles RubyToken::TkMODULE
|
3
|
+
|
4
|
+
def process
|
5
|
+
module_name = move_to_namespace(statement.tokens[2].text)
|
6
|
+
child = YARD::ModuleObject.new(module_name, object, statement.comments)
|
7
|
+
enter_namespace(child) { parse_block }
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class YARD::YieldHandler < YARD::CodeObjectHandler
|
2
|
+
handles 'yield'
|
3
|
+
|
4
|
+
def process
|
5
|
+
tokens = statement.tokens.reject {|tk| [RubyToken::TkSPACE, RubyToken::TkLPAREN].include? tk.class }
|
6
|
+
from = tokens.each_with_index do |token, index|
|
7
|
+
break index if token.class == RubyToken::TkYIELD
|
8
|
+
end
|
9
|
+
if from.is_a? Fixnum
|
10
|
+
params = []
|
11
|
+
(from+1).step(tokens.size-1, 2) do |index|
|
12
|
+
# FIXME: This won't work if the yield has a method call or complex constant name (A::B)
|
13
|
+
params << tokens[index].text
|
14
|
+
break unless tokens[index+1].is_a? RubyToken::TkCOMMA
|
15
|
+
end
|
16
|
+
|
17
|
+
# Only add the tags if none were added at all
|
18
|
+
if object.tags("yieldparam").empty? && object.tags("yield").empty?
|
19
|
+
params.each do |param|
|
20
|
+
# TODO: We can technically introspect any constant to find out parameter types,
|
21
|
+
# not just self.
|
22
|
+
# If parameter is self, we have a chance to get some extra information
|
23
|
+
if param == "self"
|
24
|
+
param = "[#{object.parent.path}] _self the object that yields the value (self)"
|
25
|
+
end
|
26
|
+
object.tags << YARD::TagLibrary.yieldparam_tag(param)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/namespace.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'find'
|
3
|
+
require 'yaml'
|
4
|
+
require File.dirname(__FILE__) + '/code_object'
|
5
|
+
require File.dirname(__FILE__) + '/source_parser'
|
6
|
+
|
7
|
+
module YARD
|
8
|
+
class Namespace
|
9
|
+
DEFAULT_YARDOC_FILE = "_Yardoc"
|
10
|
+
|
11
|
+
include Singleton
|
12
|
+
|
13
|
+
class << self
|
14
|
+
##
|
15
|
+
# Attempt to find a namespace and return it if it exists. Similar
|
16
|
+
# to the {YARD::Namsepace::at} method but creates the namespace
|
17
|
+
# as a module if it does not exist, and return it.
|
18
|
+
#
|
19
|
+
# @param [String] namespace the namespace to search for.
|
20
|
+
# @return [CodeObject] the namespace that was found or created.
|
21
|
+
def find_or_create_namespace(namespace)
|
22
|
+
return at(namespace) if at(namespace)
|
23
|
+
name = namespace.split("::").last
|
24
|
+
object = ModuleObject.new(name)
|
25
|
+
instance.namespace.update(namespace => object)
|
26
|
+
object
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_object(object)
|
30
|
+
instance.add_object(object)
|
31
|
+
end
|
32
|
+
|
33
|
+
def at(name)
|
34
|
+
instance.at(name)
|
35
|
+
end
|
36
|
+
alias_method :[], :at
|
37
|
+
|
38
|
+
def root
|
39
|
+
at('')
|
40
|
+
end
|
41
|
+
|
42
|
+
def all
|
43
|
+
instance.namespace.keys
|
44
|
+
end
|
45
|
+
|
46
|
+
def each_object
|
47
|
+
instance.namespace.each do |name, object|
|
48
|
+
yield(name, object)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def load(file = DEFAULT_YARDOC_FILE, reload = false)
|
53
|
+
if File.exists?(file) && !reload
|
54
|
+
instance.namespace.replace(Marshal.load(IO.read(file)))
|
55
|
+
else
|
56
|
+
Find.find(".") do |path|
|
57
|
+
SourceParser.parse(path) if path =~ /\.rb$/
|
58
|
+
end
|
59
|
+
end
|
60
|
+
save
|
61
|
+
end
|
62
|
+
|
63
|
+
def save(file = DEFAULT_YARDOC_FILE)
|
64
|
+
File.open(file, "w") {|f| Marshal.dump(instance.namespace, f) }
|
65
|
+
end
|
66
|
+
|
67
|
+
def find_from_path(object, name)
|
68
|
+
object = at(object) unless object.is_a? CodeObject
|
69
|
+
return object if name == 'self'
|
70
|
+
|
71
|
+
while object
|
72
|
+
["::", ""].each do |type|
|
73
|
+
obj = at(object.path + type + name)
|
74
|
+
return obj if obj
|
75
|
+
end
|
76
|
+
object = object.parent
|
77
|
+
end
|
78
|
+
nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
attr_reader :namespace
|
83
|
+
|
84
|
+
def initialize
|
85
|
+
@namespace = { '' => CodeObjectWithMethods.new('', :root) }
|
86
|
+
end
|
87
|
+
|
88
|
+
def add_object(object)
|
89
|
+
return object if namespace[object.path] && object.docstring.nil?
|
90
|
+
namespace.update(object.path => object)
|
91
|
+
object
|
92
|
+
end
|
93
|
+
|
94
|
+
def at(name)
|
95
|
+
namespace[name]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
data/lib/quick_doc.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/namespace'
|
2
|
+
|
3
|
+
module YARD
|
4
|
+
class QuickDoc
|
5
|
+
def initialize(name)
|
6
|
+
Namespace.load
|
7
|
+
if name.nil?
|
8
|
+
puts "Method Listing:"
|
9
|
+
puts "---------------"
|
10
|
+
puts Namespace.all.select {|n| Namespace.at(n).is_a? MethodObject }.sort.join("\n")
|
11
|
+
return
|
12
|
+
end
|
13
|
+
|
14
|
+
meth = Namespace.at(name)
|
15
|
+
if meth.nil?
|
16
|
+
puts "No entry for #{name}"
|
17
|
+
return
|
18
|
+
end
|
19
|
+
|
20
|
+
ns = meth.path
|
21
|
+
rvalue = meth.tag('return')
|
22
|
+
return_type = rvalue && rvalue.type ? rvalue.type : "undefined"
|
23
|
+
block = nil
|
24
|
+
unless meth.tags("yieldparam").empty?
|
25
|
+
block = " {|" + meth.tags("yieldparam").collect {|tag| tag.name }.join(", ") + "| ... }"
|
26
|
+
end
|
27
|
+
|
28
|
+
puts "Documentation for #{ns}"
|
29
|
+
puts "==================#{'=' * ns.length}"
|
30
|
+
if meth.file
|
31
|
+
puts "File: '#{meth.file}' (on line #{meth.line})"
|
32
|
+
end
|
33
|
+
puts "Type: #{return_type}\t\tVisibility: #{meth.visibility}"
|
34
|
+
puts
|
35
|
+
if meth.tag('deprecated')
|
36
|
+
desc = meth.tag('deprecrated').text
|
37
|
+
puts
|
38
|
+
puts "!! This method is deprecated" + (desc ? ": #{desc}" : ".")
|
39
|
+
puts
|
40
|
+
end
|
41
|
+
if meth.docstring
|
42
|
+
puts word_wrap(meth.docstring, ns.length + 18)
|
43
|
+
puts
|
44
|
+
end
|
45
|
+
unless meth.tags("param").empty? && meth.tags("raise").empty? && meth.tags("return").empty?
|
46
|
+
puts "Meta Tags:"
|
47
|
+
puts "----------"
|
48
|
+
meth.tags("param").each do |tag|
|
49
|
+
types = tag.types.empty? ? "" : "[#{tag.types.join(", ")}] "
|
50
|
+
puts "> Parameter: #{types}#{tag.name} => #{tag.text}"
|
51
|
+
end
|
52
|
+
meth.tags("raise").each do |tag|
|
53
|
+
puts "> Raises #{tag.name} exception#{tag.text ? ': ' + tag.text : ''}"
|
54
|
+
end
|
55
|
+
meth.tags("return").each do |tag|
|
56
|
+
puts "> Returns #{tag.types.empty? ? "" : "(as " + tag.types.join(", ") + ')'} #{tag.text}"
|
57
|
+
end
|
58
|
+
puts
|
59
|
+
unless meth.tags("yieldparam").empty?
|
60
|
+
puts "Yields:"
|
61
|
+
puts "-------"
|
62
|
+
meth.tags("yieldparam").each do |tag|
|
63
|
+
types = tag.types.empty? ? "" : "[#{tag.types.join(", ")}] "
|
64
|
+
puts "> Block parameter: #{types}#{tag.name} => #{tag.text}"
|
65
|
+
puts
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
if meth.source
|
70
|
+
puts "Definition:"
|
71
|
+
puts "-----------"
|
72
|
+
puts format_code(meth.source.sub("\n", "#{block}" + (return_type ? " # -> " + return_type : "") + "\n"))
|
73
|
+
puts
|
74
|
+
end
|
75
|
+
unless meth.tags("see").empty?
|
76
|
+
puts "See Also:"
|
77
|
+
puts "---------"
|
78
|
+
meth.tags("see").each do |tag|
|
79
|
+
puts "\t- #{tag.text}"
|
80
|
+
end
|
81
|
+
puts
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
protected
|
86
|
+
def word_wrap(text, length = 80)
|
87
|
+
text.gsub(/(.{0,#{length - 3}}\s)/, "\n" + '\1')
|
88
|
+
end
|
89
|
+
|
90
|
+
def format_code(text, indent_size = 2)
|
91
|
+
last_indent, tab = nil, 0
|
92
|
+
text.split(/\r?\n/).collect do |line|
|
93
|
+
indent = line[/^(\s*)/, 1].length
|
94
|
+
if last_indent && indent > last_indent
|
95
|
+
tab += indent_size
|
96
|
+
elsif last_indent && indent < last_indent
|
97
|
+
tab -= indent_size
|
98
|
+
end
|
99
|
+
last_indent = indent
|
100
|
+
(" " * tab) + line.sub(/^\s*/, '')
|
101
|
+
end.join("\n")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|