Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Initial commit

  • Loading branch information...
commit 38df0e96da4c1d45abc586f4366d1f1dfb8ff19d 0 parents
David Demaree authored
17 .gitignore
... ... @@ -0,0 +1,17 @@
  1 +*.gem
  2 +*.rbc
  3 +.bundle
  4 +.config
  5 +.yardoc
  6 +Gemfile.lock
  7 +InstalledFiles
  8 +_yardoc
  9 +coverage
  10 +doc/
  11 +lib/bundler/man
  12 +pkg
  13 +rdoc
  14 +spec/reports
  15 +test/tmp
  16 +test/version_tmp
  17 +tmp
6 Gemfile
... ... @@ -0,0 +1,6 @@
  1 +source 'https://rubygems.org'
  2 +
  3 +# Specify your gem's dependencies in annotations.gemspec
  4 +gemspec
  5 +
  6 +# TODO: Test me
27 LICENSE
... ... @@ -0,0 +1,27 @@
  1 +Copyright (c) 2012 David Demaree
  2 +
  3 +MIT License
  4 +
  5 +Permission is hereby granted, free of charge, to any person obtaining
  6 +a copy of this software and associated documentation files (the
  7 +"Software"), to deal in the Software without restriction, including
  8 +without limitation the rights to use, copy, modify, merge, publish,
  9 +distribute, sublicense, and/or sell copies of the Software, and to
  10 +permit persons to whom the Software is furnished to do so, subject to
  11 +the following conditions:
  12 +
  13 +The above copyright notice and this permission notice shall be
  14 +included in all copies or substantial portions of the Software.
  15 +
  16 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  17 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  18 +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  19 +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  20 +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  21 +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  22 +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  23 +
  24 +Portions of this software are extracted from Railties, part of the Ruby
  25 +on Rails project (http://github.com/rails/rails), copyright (c) 2004-2011
  26 +David Heinemeier Hansson. Ruby on Rails is open source software released
  27 +under the MIT license.
105 README.md
Source Rendered
... ... @@ -0,0 +1,105 @@
  1 +# Annotations
  2 +
  3 +Extracts and displays annotations from source code comments like these:
  4 +
  5 + class MyModel
  6 + def find(id)
  7 + # TODO: Find the thing
  8 + end
  9 + end
  10 +
  11 +The output looks like this:
  12 +
  13 + ./lib/my_model.rb:
  14 + * [ 17] [TODO] Find the thing
  15 +
  16 +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.
  17 +
  18 +Annotations looks for TODO, FIXME, and OPTIMIZE comments in the following kinds of source code files:
  19 +
  20 +<table>
  21 + <thead>
  22 + <tr class="header-row">
  23 + <th>Syntax</th>
  24 + <th>Supported file extensions</th>
  25 + </tr>
  26 + </thead>
  27 + <tbody>
  28 + <tr>
  29 + <td><b>Ruby</b></td>
  30 + <td>.rb, .builder, Gemfile, Rakefile</td>
  31 + </tr>
  32 + <tr>
  33 + <td><b>ERb</b></td>
  34 + <td>.erb, .rhtml</td>
  35 + </tr>
  36 + <tr>
  37 + <td><b>CoffeeScript</b></td>
  38 + <td>.coffee</td>
  39 + </tr>
  40 + <tr>
  41 + <td><b>Sass</b></td>
  42 + <td>.scss, .sass</td>
  43 + </tr>
  44 + <tr>
  45 + <td><b>PHP</b></td>
  46 + <td>.php</td>
  47 + </tr>
  48 + </tbody>
  49 +</table>
  50 +
  51 +## Installation
  52 +
  53 +Add this line to your application's Gemfile:
  54 +
  55 + gem 'annotations'
  56 +
  57 +Or install it yourself as:
  58 +
  59 + $ gem install annotations
  60 +
  61 +## Usage
  62 +
  63 +Add the Annotations tasks to your Rakefile:
  64 +
  65 +```ruby
  66 +require 'annotations/rake_task'
  67 +
  68 +Annotations::RakeTask.new
  69 +```
  70 +
  71 +This will add the following tasks:
  72 +
  73 + $ bundle exec rake -T notes
  74 + rake notes # Enumerate all annotations
  75 + rake notes:custom[annotation] # Enumerate a custom annotation
  76 + rake notes:fixme # Enumerate all FIXME annotations
  77 + rake notes:optimize # Enumerate all OPTIMIZE annotations
  78 + rake notes:todo # Enumerate all TODO annotations
  79 +
  80 +If you want to name the tasks something other than "notes", just pass the name you want to use into `RakeTask.new`:
  81 +
  82 +```ruby
  83 +Annotations::RakeTask.new(:devnotes)
  84 +```
  85 +
  86 +You can also set the default tag list when defining the task, using this block syntax:
  87 +
  88 +```ruby
  89 +Annotations::RakeTask.new do |t|
  90 + # This will add an additional 'WTF' annotation; it will be included in
  91 + # `rake notes`, and a `rake notes:wtf` task will be added
  92 + t.tags = [:fixme, :optimize, :todo, :wtf]
  93 +end
  94 +```
  95 +
  96 +## Roadmap
  97 +
  98 +* Ability to set/limit the search path(s) for annotations (currently set to '.')
  99 +* Color output
  100 +* Standalone command-line tool (e.g. `annotations wtf todo --color`)
  101 +* More robust handling of different extensions/comment formats, plus the ability to easily add in new ones
  102 +
  103 +## Contributing
  104 +
  105 +Fork the project, make some changes on a feature branch, then send a pull request.
5 Rakefile
... ... @@ -0,0 +1,5 @@
  1 +#!/usr/bin/env rake
  2 +require "bundler/gem_tasks"
  3 +require "annotations/rake_task"
  4 +
  5 +Annotations::RakeTask.new(:notes)
19 annotations.gemspec
... ... @@ -0,0 +1,19 @@
  1 +# -*- encoding: utf-8 -*-
  2 +require File.expand_path('../lib/annotations/version', __FILE__)
  3 +
  4 +Gem::Specification.new do |gem|
  5 + gem.authors = ["David Demaree"]
  6 + gem.email = ["ddemaree@gmail.com"]
  7 + gem.description = %q{TODO: Write a gem description}
  8 + gem.summary = %q{TODO: Write a gem summary}
  9 + gem.homepage = ""
  10 +
  11 + gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
  12 + gem.files = `git ls-files`.split("\n")
  13 + gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
  14 + gem.name = "annotations"
  15 + gem.require_paths = ["lib"]
  16 + gem.version = Annotations::VERSION
  17 +
  18 + gem.add_runtime_dependency "rake"
  19 +end
6 lib/annotations.rb
... ... @@ -0,0 +1,6 @@
  1 +require "annotations/version"
  2 +
  3 +module Annotations
  4 +end
  5 +
  6 +require "annotations/extractor"
114 lib/annotations/extractor.rb
... ... @@ -0,0 +1,114 @@
  1 +module Annotations
  2 + # Implements the logic behind the rake tasks for annotations like
  3 + #
  4 + # rake notes
  5 + # rake notes:optimize
  6 + #
  7 + # and friends. See <tt>rake -T notes</tt> and <tt>railties/lib/tasks/annotations.rake</tt>.
  8 + #
  9 + # Annotation objects are triplets <tt>:line</tt>, <tt>:tag</tt>, <tt>:text</tt> that
  10 + # represent the line where the annotation lives, its tag, and its text. Note
  11 + # the filename is not stored.
  12 + #
  13 + # Annotations are looked for in comments and modulus whitespace they have to
  14 + # start with the tag optionally followed by a colon. Everything up to the end
  15 + # of the line (or closing ERB comment tag) is considered to be their text.
  16 + class Extractor
  17 + # TODO: Test me
  18 + class Annotation < Struct.new(:line, :tag, :text)
  19 +
  20 + # Returns a representation of the annotation that looks like this:
  21 + #
  22 + # [126] [TODO] This algorithm is simple and clearly correct, make it faster.
  23 + #
  24 + # If +options+ has a flag <tt>:tag</tt> the tag is shown as in the example above.
  25 + # Otherwise the string contains just line and text.
  26 + def to_s(options={})
  27 + s = "[%3d] " % line
  28 + s << "[#{tag}] " if options[:tag]
  29 + s << text
  30 + end
  31 + end
  32 +
  33 + # Prints all annotations with tag +tag+ under the root directories +app+, +lib+,
  34 + # and +test+ (recursively). Only filenames with extension +.builder+, +.rb+,
  35 + # +.rxml+, +.rhtml+, or +.erb+ are taken into account. The +options+
  36 + # hash is passed to each annotation's +to_s+.
  37 + #
  38 + # This class method is the single entry point for the rake tasks.
  39 + def self.enumerate(tag, options={})
  40 + extractor = new(tag)
  41 + extractor.display(extractor.find, options)
  42 + end
  43 +
  44 + attr_reader :tag
  45 +
  46 + def initialize(tag)
  47 + @tag = tag
  48 + end
  49 +
  50 + # Returns a hash that maps filenames under +dirs+ (recursively) to arrays
  51 + # with their annotations. Only files with annotations are included, and only
  52 + # those with extension +.builder+, +.rb+, +.rxml+, +.rhtml+, and +.erb+
  53 + # are taken into account.
  54 + # TODO: Make dirs configurable
  55 + def find(dirs=%w(.))
  56 + dirs.inject({}) { |h, dir| h.update(find_in(dir)) }
  57 + end
  58 +
  59 + # Returns a hash that maps filenames under +dir+ (recursively) to arrays
  60 + # with their annotations. Only files with annotations are included, and only
  61 + # those with extension +.builder+, +.rb+, +.rxml+, +.rhtml+, and +.erb+
  62 + # are taken into account.
  63 + def find_in(dir)
  64 + results = {}
  65 +
  66 + Dir.glob("#{dir}/*") do |item|
  67 + next if File.basename(item)[0] == ?.
  68 +
  69 + if File.directory?(item)
  70 + results.update(find_in(item))
  71 + # Ruby source code
  72 + elsif item =~ /\.(ru|builder|coffee|(r(?:b|xml|js)))$/
  73 + results.update(extract_annotations_from(item, /#\s*(#{tag}):?\s*(.*)$/))
  74 + # Other Ruby source files (Rake, Bundler)
  75 + elsif item =~ /(Rakefile|Gemfile|Guardfile)$/
  76 + results.update(extract_annotations_from(item, /#\s*(#{tag}):?\s*(.*)$/))
  77 + # ERb templates
  78 + elsif item =~ /\.(rhtml|erb)$/
  79 + results.update(extract_annotations_from(item, /<%\s*#\s*(#{tag}):?\s*(.*?)\s*%>/))
  80 + # Sass, JavaScript, PHP, other source with double-slash comments
  81 + elsif item =~ /\.(s(?:a|c)ss|js|php)$/
  82 + results.update(extract_annotations_from(item, /\/\/\s*(#{tag}):?\s*(.*)$/))
  83 + end
  84 + end
  85 +
  86 + results
  87 + end
  88 +
  89 + # If +file+ is the filename of a file that contains annotations this method returns
  90 + # a hash with a single entry that maps +file+ to an array of its annotations.
  91 + # Otherwise it returns an empty hash.
  92 + def extract_annotations_from(file, pattern)
  93 + lineno = 0
  94 + result = File.readlines(file).inject([]) do |list, line|
  95 + lineno += 1
  96 + next list unless line =~ pattern
  97 + list << Annotation.new(lineno, $1, $2)
  98 + end
  99 + result.empty? ? {} : { file => result }
  100 + end
  101 +
  102 + # Prints the mapping from filenames to annotations in +results+ ordered by filename.
  103 + # The +options+ hash is passed to each annotation's +to_s+.
  104 + def display(results, options={})
  105 + results.keys.sort.each do |file|
  106 + puts "#{file}:"
  107 + results[file].each do |note|
  108 + puts " * #{note.to_s(options)}"
  109 + end
  110 + puts
  111 + end
  112 + end
  113 + end
  114 +end
70 lib/annotations/rake_task.rb
... ... @@ -0,0 +1,70 @@
  1 +require 'rake'
  2 +require 'rake/tasklib'
  3 +require 'annotations'
  4 +
  5 +module Annotations
  6 + class RakeTask < ::Rake::TaskLib
  7 +
  8 + attr_accessor :name, :tags
  9 +
  10 + def initialize(name = :notes)
  11 + @name = name
  12 + @tags = [:optimize, :fixme, :todo]
  13 + define
  14 + end
  15 +
  16 + def tags_to_pattern
  17 + @tags.map { |t| t.to_s.upcase }.join("|")
  18 + end
  19 +
  20 + # Define tasks
  21 + def define
  22 + desc "Enumerate all annotations"
  23 + task name do
  24 + Annotations::Extractor.enumerate(tags_to_pattern, :tag => true)
  25 + end
  26 +
  27 + namespace name do
  28 + tags.each do |tagname|
  29 + desc "Enumerate all #{tagname.to_s.upcase} annotations"
  30 + task tagname.to_sym do
  31 + Annotations::Extractor.enumerate(tagname.to_s.upcase, :tag => true)
  32 + end
  33 + end
  34 +
  35 + desc "Enumerate a custom annotation, specify with ANNOTATION=CUSTOM"
  36 + task :custom, :annotation do |annotation|
  37 + puts annotation
  38 + SourceAnnotationExtractor.enumerate ENV['ANNOTATION']
  39 + end
  40 + end
  41 +
  42 + # desc name == :notes ? "Compile assets" : "Compile #{name} assets"
  43 + # desc "Enumerate all annotations (use notes:optimize, :fixme, :todo for focus)"
  44 + # task name do
  45 + # with_logger do
  46 + # manifest.compile(assets)
  47 + # end
  48 + # end
  49 +
  50 + # desc name == :assets ? "Remove all assets" : "Remove all #{name} assets"
  51 + # task "clobber_#{name}" do
  52 + # with_logger do
  53 + # manifest.clobber
  54 + # end
  55 + # end
  56 +
  57 + # task :clobber => ["clobber_#{name}"]
  58 +
  59 + # desc name == :assets ? "Clean old assets" : "Clean old #{name} assets"
  60 + # task "clean_#{name}" do
  61 + # with_logger do
  62 + # manifest.clean(keep)
  63 + # end
  64 + # end
  65 +
  66 + # task :clean => ["clean_#{name}"]
  67 + end
  68 +
  69 + end
  70 +end
3  lib/annotations/version.rb
... ... @@ -0,0 +1,3 @@
  1 +module Annotations
  2 + VERSION = "0.1.0"
  3 +end

0 comments on commit 38df0e9

Please sign in to comment.
Something went wrong with that request. Please try again.