Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
ddemaree committed Feb 18, 2012
0 parents commit 38df0e9
Show file tree
Hide file tree
Showing 10 changed files with 372 additions and 0 deletions.
17 changes: 17 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
*.gem
*.rbc
.bundle
.config
.yardoc
Gemfile.lock
InstalledFiles
_yardoc
coverage
doc/
lib/bundler/man
pkg
rdoc
spec/reports
test/tmp
test/version_tmp
tmp
6 changes: 6 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
source 'https://rubygems.org'

# Specify your gem's dependencies in annotations.gemspec
gemspec

# TODO: Test me
27 changes: 27 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Copyright (c) 2012 David Demaree

MIT License

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Portions of this software are extracted from Railties, part of the Ruby
on Rails project (http://github.com/rails/rails), copyright (c) 2004-2011
David Heinemeier Hansson. Ruby on Rails is open source software released
under the MIT license.
105 changes: 105 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Annotations

Extracts and displays annotations from source code comments like these:

class MyModel
def find(id)
# TODO: Find the thing
end
end

The output looks like this:

./lib/my_model.rb:
* [ 17] [TODO] Find the thing

If this looks familiar from Rails, it's because Annotations is derived/forked from the annotations code in Rails 3.2.1, now extracted into its own gem so it can be used in non-Rails (or even non-Ruby) projects.

Annotations looks for TODO, FIXME, and OPTIMIZE comments in the following kinds of source code files:

<table>
<thead>
<tr class="header-row">
<th>Syntax</th>
<th>Supported file extensions</th>
</tr>
</thead>
<tbody>
<tr>
<td><b>Ruby</b></td>
<td>.rb, .builder, Gemfile, Rakefile</td>
</tr>
<tr>
<td><b>ERb</b></td>
<td>.erb, .rhtml</td>
</tr>
<tr>
<td><b>CoffeeScript</b></td>
<td>.coffee</td>
</tr>
<tr>
<td><b>Sass</b></td>
<td>.scss, .sass</td>
</tr>
<tr>
<td><b>PHP</b></td>
<td>.php</td>
</tr>
</tbody>
</table>

## Installation

Add this line to your application's Gemfile:

gem 'annotations'

Or install it yourself as:

$ gem install annotations

## Usage

Add the Annotations tasks to your Rakefile:

```ruby
require 'annotations/rake_task'

Annotations::RakeTask.new
```

This will add the following tasks:

$ bundle exec rake -T notes
rake notes # Enumerate all annotations
rake notes:custom[annotation] # Enumerate a custom annotation
rake notes:fixme # Enumerate all FIXME annotations
rake notes:optimize # Enumerate all OPTIMIZE annotations
rake notes:todo # Enumerate all TODO annotations

If you want to name the tasks something other than "notes", just pass the name you want to use into `RakeTask.new`:

```ruby
Annotations::RakeTask.new(:devnotes)
```

You can also set the default tag list when defining the task, using this block syntax:

```ruby
Annotations::RakeTask.new do |t|
# This will add an additional 'WTF' annotation; it will be included in
# `rake notes`, and a `rake notes:wtf` task will be added
t.tags = [:fixme, :optimize, :todo, :wtf]
end
```

## Roadmap

* Ability to set/limit the search path(s) for annotations (currently set to '.')
* Color output
* Standalone command-line tool (e.g. `annotations wtf todo --color`)
* More robust handling of different extensions/comment formats, plus the ability to easily add in new ones

## Contributing

Fork the project, make some changes on a feature branch, then send a pull request.
5 changes: 5 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env rake
require "bundler/gem_tasks"
require "annotations/rake_task"

Annotations::RakeTask.new(:notes)
19 changes: 19 additions & 0 deletions annotations.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# -*- encoding: utf-8 -*-
require File.expand_path('../lib/annotations/version', __FILE__)

Gem::Specification.new do |gem|
gem.authors = ["David Demaree"]
gem.email = ["ddemaree@gmail.com"]
gem.description = %q{TODO: Write a gem description}
gem.summary = %q{TODO: Write a gem summary}
gem.homepage = ""

gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
gem.files = `git ls-files`.split("\n")
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
gem.name = "annotations"
gem.require_paths = ["lib"]
gem.version = Annotations::VERSION

gem.add_runtime_dependency "rake"
end
6 changes: 6 additions & 0 deletions lib/annotations.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
require "annotations/version"

module Annotations
end

require "annotations/extractor"
114 changes: 114 additions & 0 deletions lib/annotations/extractor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
module Annotations
# Implements the logic behind the rake tasks for annotations like
#
# rake notes
# rake notes:optimize
#
# and friends. See <tt>rake -T notes</tt> and <tt>railties/lib/tasks/annotations.rake</tt>.
#
# Annotation objects are triplets <tt>:line</tt>, <tt>:tag</tt>, <tt>:text</tt> that
# represent the line where the annotation lives, its tag, and its text. Note
# the filename is not stored.
#
# Annotations are looked for in comments and modulus whitespace they have to
# start with the tag optionally followed by a colon. Everything up to the end
# of the line (or closing ERB comment tag) is considered to be their text.
class Extractor
# TODO: Test me
class Annotation < Struct.new(:line, :tag, :text)

# Returns a representation of the annotation that looks like this:
#
# [126] [TODO] This algorithm is simple and clearly correct, make it faster.
#
# If +options+ has a flag <tt>:tag</tt> the tag is shown as in the example above.
# Otherwise the string contains just line and text.
def to_s(options={})
s = "[%3d] " % line
s << "[#{tag}] " if options[:tag]
s << text
end
end

# Prints all annotations with tag +tag+ under the root directories +app+, +lib+,
# and +test+ (recursively). Only filenames with extension +.builder+, +.rb+,
# +.rxml+, +.rhtml+, or +.erb+ are taken into account. The +options+
# hash is passed to each annotation's +to_s+.
#
# This class method is the single entry point for the rake tasks.
def self.enumerate(tag, options={})
extractor = new(tag)
extractor.display(extractor.find, options)
end

attr_reader :tag

def initialize(tag)
@tag = tag
end

# Returns a hash that maps filenames under +dirs+ (recursively) to arrays
# with their annotations. Only files with annotations are included, and only
# those with extension +.builder+, +.rb+, +.rxml+, +.rhtml+, and +.erb+
# are taken into account.
# TODO: Make dirs configurable
def find(dirs=%w(.))
dirs.inject({}) { |h, dir| h.update(find_in(dir)) }
end

# Returns a hash that maps filenames under +dir+ (recursively) to arrays
# with their annotations. Only files with annotations are included, and only
# those with extension +.builder+, +.rb+, +.rxml+, +.rhtml+, and +.erb+
# are taken into account.
def find_in(dir)
results = {}

Dir.glob("#{dir}/*") do |item|
next if File.basename(item)[0] == ?.

if File.directory?(item)
results.update(find_in(item))
# Ruby source code
elsif item =~ /\.(ru|builder|coffee|(r(?:b|xml|js)))$/
results.update(extract_annotations_from(item, /#\s*(#{tag}):?\s*(.*)$/))
# Other Ruby source files (Rake, Bundler)
elsif item =~ /(Rakefile|Gemfile|Guardfile)$/
results.update(extract_annotations_from(item, /#\s*(#{tag}):?\s*(.*)$/))
# ERb templates
elsif item =~ /\.(rhtml|erb)$/
results.update(extract_annotations_from(item, /<%\s*#\s*(#{tag}):?\s*(.*?)\s*%>/))
# Sass, JavaScript, PHP, other source with double-slash comments
elsif item =~ /\.(s(?:a|c)ss|js|php)$/
results.update(extract_annotations_from(item, /\/\/\s*(#{tag}):?\s*(.*)$/))
end
end

results
end

# If +file+ is the filename of a file that contains annotations this method returns
# a hash with a single entry that maps +file+ to an array of its annotations.
# Otherwise it returns an empty hash.
def extract_annotations_from(file, pattern)
lineno = 0
result = File.readlines(file).inject([]) do |list, line|
lineno += 1
next list unless line =~ pattern
list << Annotation.new(lineno, $1, $2)
end
result.empty? ? {} : { file => result }
end

# Prints the mapping from filenames to annotations in +results+ ordered by filename.
# The +options+ hash is passed to each annotation's +to_s+.
def display(results, options={})
results.keys.sort.each do |file|
puts "#{file}:"
results[file].each do |note|
puts " * #{note.to_s(options)}"
end
puts
end
end
end
end
70 changes: 70 additions & 0 deletions lib/annotations/rake_task.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
require 'rake'
require 'rake/tasklib'
require 'annotations'

module Annotations
class RakeTask < ::Rake::TaskLib

attr_accessor :name, :tags

def initialize(name = :notes)
@name = name
@tags = [:optimize, :fixme, :todo]
define
end

def tags_to_pattern
@tags.map { |t| t.to_s.upcase }.join("|")
end

# Define tasks
def define
desc "Enumerate all annotations"
task name do
Annotations::Extractor.enumerate(tags_to_pattern, :tag => true)
end

namespace name do
tags.each do |tagname|
desc "Enumerate all #{tagname.to_s.upcase} annotations"
task tagname.to_sym do
Annotations::Extractor.enumerate(tagname.to_s.upcase, :tag => true)
end
end

desc "Enumerate a custom annotation, specify with ANNOTATION=CUSTOM"
task :custom, :annotation do |annotation|
puts annotation
SourceAnnotationExtractor.enumerate ENV['ANNOTATION']
end
end

# desc name == :notes ? "Compile assets" : "Compile #{name} assets"
# desc "Enumerate all annotations (use notes:optimize, :fixme, :todo for focus)"
# task name do
# with_logger do
# manifest.compile(assets)
# end
# end

# desc name == :assets ? "Remove all assets" : "Remove all #{name} assets"
# task "clobber_#{name}" do
# with_logger do
# manifest.clobber
# end
# end

# task :clobber => ["clobber_#{name}"]

# desc name == :assets ? "Clean old assets" : "Clean old #{name} assets"
# task "clean_#{name}" do
# with_logger do
# manifest.clean(keep)
# end
# end

# task :clean => ["clean_#{name}"]
end

end
end
3 changes: 3 additions & 0 deletions lib/annotations/version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Annotations
VERSION = "0.1.0"
end

0 comments on commit 38df0e9

Please sign in to comment.