Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial commit

  • Loading branch information...
commit 38df0e96da4c1d45abc586f4366d1f1dfb8ff19d 0 parents
@ddemaree authored
17 .gitignore
@@ -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 Gemfile
@@ -0,0 +1,6 @@
+source 'https://rubygems.org'
+
+# Specify your gem's dependencies in annotations.gemspec
+gemspec
+
+# TODO: Test me
27 LICENSE
@@ -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 README.md
@@ -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 Rakefile
@@ -0,0 +1,5 @@
+#!/usr/bin/env rake
+require "bundler/gem_tasks"
+require "annotations/rake_task"
+
+Annotations::RakeTask.new(:notes)
19 annotations.gemspec
@@ -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 lib/annotations.rb
@@ -0,0 +1,6 @@
+require "annotations/version"
+
+module Annotations
+end
+
+require "annotations/extractor"
114 lib/annotations/extractor.rb
@@ -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 lib/annotations/rake_task.rb
@@ -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  lib/annotations/version.rb
@@ -0,0 +1,3 @@
+module Annotations
+ VERSION = "0.1.0"
+end
Please sign in to comment.
Something went wrong with that request. Please try again.