1 # Implements the logic behind the rake tasks for annotations like
6 # and friends. See <tt>rake -T notes</tt> and <tt>railties/lib/tasks/annotations.rake</tt>.
8 # Annotation objects are triplets <tt>:line</tt>, <tt>:tag</tt>, <tt>:text</tt> that
9 # represent the line where the annotation lives, its tag, and its text. Note
10 # the filename is not stored.
12 # Annotations are looked for in comments and modulus whitespace they have to
13 # start with the tag optionally followed by a colon. Everything up to the end
14 # of the line (or closing ERb comment tag) is considered to be their text.
15 class SourceAnnotationExtractor
16 class Annotation
< Struct
.new(:line, :tag, :text)
18 # Returns a representation of the annotation that looks like this:
20 # [126] [TODO] This algorithm is simple and clearly correct, make it faster.
22 # If +options+ has a flag <tt>:tag</tt> the tag is shown as in the example above.
23 # Otherwise the string contains just line and text.
26 s
<< "[#{tag}] " if options
[:tag]
31 # Prints all annotations with tag +tag+ under the root directories +app+, +lib+,
32 # and +test+ (recursively). Only filenames with extension +.builder+, +.rb+,
33 # +.rxml+, +.rjs+, +.rhtml+, or +.erb+ are taken into account. The +options+
34 # hash is passed to each annotation's +to_s+.
36 # This class method is the single entry point for the rake tasks.
37 def self.enumerate(tag
, options
={})
39 extractor
.display(extractor
.find
, options
)
48 # Returns a hash that maps filenames under +dirs+ (recursively) to arrays
49 # with their annotations. Only files with annotations are included, and only
50 # those with extension +.builder+, +.rb+, +.rxml+, +.rjs+, +.rhtml+, and +.erb+
51 # are taken into account.
52 def find(dirs
=%w(app lib test
))
53 dirs
.inject({}) { |h
, dir
| h
.update(find_in(dir
)) }
56 # Returns a hash that maps filenames under +dir+ (recursively) to arrays
57 # with their annotations. Only files with annotations are included, and only
58 # those with extension +.builder+, +.rb+, +.rxml+, +.rjs+, +.rhtml+, and +.erb+
59 # are taken into account.
63 Dir
.glob("#{dir}/*") do |item
|
64 next if File
.basename(item
)[0] == ?.
66 if File
.directory
?(item
)
67 results
.update(find_in(item
))
68 elsif item
=~
/\.(builder|(r(?:b|xml|js)))$/
69 results
.update(extract_annotations_from(item
, /#\s*(#{tag}):?\s*(.*)$/))
70 elsif item
=~
/\.(rhtml|erb)$/
71 results
.update(extract_annotations_from(item
, /<%\s*#\s*(#{tag}):?\s*(.*?)\s*%>/))
78 # If +file+ is the filename of a file that contains annotations this method returns
79 # a hash with a single entry that maps +file+ to an array of its annotations.
80 # Otherwise it returns an empty hash.
81 def extract_annotations_from(file
, pattern
)
83 result
= File
.readlines(file
).inject([]) do |list
, line
|
85 next list
unless line
=~ pattern
86 list
<< Annotation
.new(lineno
, $1, $2)
88 result
.empty
? ? {} : { file
=> result
}
91 # Prints the mapping from filenames to annotations in +results+ ordered by filename.
92 # The +options+ hash is passed to each annotation's +to_s+.
93 def display(results
, options
={})
94 results
.keys
.sort
.each
do |file
|
96 results
[file
].each
do |note
|
97 puts
" * #{note.to_s(options)}"