Commit ebc1b037 by Jon Frisby

Overhaul options handling, and also code loading to allow standalone usage again.

parent 8e070848
== 2.5.0 == 2.5.0
* It's now possible to use Annotate in standalone ActiveRecord (non-Rails)
projects again.
* Adding note that Markdown is actually MultiMarkdown, and recommending the use * Adding note that Markdown is actually MultiMarkdown, and recommending the use
of the `kramdown` engine for parsing it. of the `kramdown` engine for parsing it.
* Improved Markdown formatting considerably. * Improved Markdown formatting considerably.
...@@ -49,7 +51,8 @@ ...@@ -49,7 +51,8 @@
* Allow task loading from Rakefile for gems (plugin installation already * Allow task loading from Rakefile for gems (plugin installation already
auto-detects). auto-detects).
* Add skip_on_db_migrate option as well for people that don't want it * Add skip_on_db_migrate option as well for people that don't want it
* Fix options parsing to convert strings to proper booleans * Fix options parsing to convert strings to proper booleans. Change annotate to
use options hash instead of ENV.
* Add support for Fabrication fabricators * Add support for Fabrication fabricators
* Leave magic encoding comment intact * Leave magic encoding comment intact
* Fix issue #14 - RuntimeError: Already memoized * Fix issue #14 - RuntimeError: Already memoized
......
...@@ -10,7 +10,7 @@ your... ...@@ -10,7 +10,7 @@ your...
- Machinist blueprints - Machinist blueprints
- Fabrication fabricators - Fabrication fabricators
- Thoughtbot's factory_girl factories, i.e. the (spec|test)/factories/<model>_factory.rb files - Thoughtbot's factory_girl factories, i.e. the (spec|test)/factories/<model>_factory.rb files
- routes.rb file - routes.rb file (for Rails projects)
The schema comment looks like this: The schema comment looks like this:
...@@ -69,6 +69,8 @@ Into environment gems from Github checkout: ...@@ -69,6 +69,8 @@ Into environment gems from Github checkout:
(If you used the Gemfile install, prefix the below commands with `bundle exec`.) (If you used the Gemfile install, prefix the below commands with `bundle exec`.)
=== Usage in Rails
To annotate all your models, tests, fixtures, and factories: To annotate all your models, tests, fixtures, and factories:
cd /path/to/app cd /path/to/app
...@@ -97,6 +99,18 @@ To remove routes.rb annotations: ...@@ -97,6 +99,18 @@ To remove routes.rb annotations:
== Configuration == Configuration
=== Usage Outside of Rails
Everything above applies, except that --routes is not meaningful, and you will
probably need to explicitly set one or more `--require` option(s), and/or one
or more `--model-dir` options to inform annotate about the structure of your
project and help it bootstrap and load the relevant code.
== Configuration
If you want to always skip annotations on a particular model, add this string If you want to always skip annotations on a particular model, add this string
anywhere in the file: anywhere in the file:
...@@ -111,6 +125,15 @@ Edit this file to control things like output format, where annotations are ...@@ -111,6 +125,15 @@ Edit this file to control things like output format, where annotations are
added (top or bottom of file), and in which artifacts. added (top or bottom of file), and in which artifacts.
=== Configuration in Rails
To generate a configuration file (in the form of a `.rake` file), to set
default options:
rails g annotate:install
Edit this file to control things like output format, where annotations are
added (top or bottom of file), and in which artifacts.
== Rails Integration == Rails Integration
By default, once you've generated a configuration file, annotate will be By default, once you've generated a configuration file, annotate will be
...@@ -135,7 +158,7 @@ you can do so with a simple environment variable, instead of editing the ...@@ -135,7 +158,7 @@ you can do so with a simple environment variable, instead of editing the
Usage: annotate [options] [model_file]* Usage: annotate [options] [model_file]*
-d, --delete Remove annotations from all model files or the routes.rb file -d, --delete Remove annotations from all model files or the routes.rb file
-p, --position [before|after] Place the annotations at the top (before) or the bottom (after) of the model/test/fixture/factory file(s) -p, --position [before|after] Place the annotations at the top (before) or the bottom (after) of the model/test/fixture/factory/routes file(s)
--pc, --position-in-class [before|after] --pc, --position-in-class [before|after]
Place the annotations at the top (before) or the bottom (after) of the model file Place the annotations at the top (before) or the bottom (after) of the model file
--pf, --position-in-factory [before|after] --pf, --position-in-factory [before|after]
......
...@@ -10,31 +10,25 @@ here = File.expand_path(File.dirname __FILE__) ...@@ -10,31 +10,25 @@ here = File.expand_path(File.dirname __FILE__)
$:<< "#{here}/../lib" $:<< "#{here}/../lib"
require 'optparse' require 'optparse'
begin
require 'rake/dsl_definition'
rescue Exception => e
# We might just be on an old version of Rake...
end
require 'rake'
require 'annotate' require 'annotate'
Annotate.bootstrap_rake
task = :annotate_models target = {
:klass => AnnotateModels,
:task => :do_annotations,
}
has_set_position = {} has_set_position = {}
OptionParser.new do |opts| OptionParser.new do |opts|
opts.banner = "Usage: annotate [options] [model_file]*" opts.banner = "Usage: annotate [options] [model_file]*"
opts.on('-d', '--delete', opts.on('-d', '--delete',
"Remove annotations from all model files or the routes.rb file") do "Remove annotations from all model files or the routes.rb file") do
if(task == :annotate_routes)
task = :remove_routes target[:task] = :remove_annotations
else
task = :remove_annotation
end
end end
opts.on('-p', '--position [before|after]', ['before', 'after'], opts.on('-p', '--position [before|after]', ['before', 'after'],
"Place the annotations at the top (before) or the bottom (after) of the model/test/fixture/factory file(s)") do |p| "Place the annotations at the top (before) or the bottom (after) of the model/test/fixture/factory/routes file(s)") do |p|
ENV['position'] = p ENV['position'] = p
[ [
'position_in_class','position_in_factory','position_in_fixture','position_in_test', 'position_in_routes' 'position_in_class','position_in_factory','position_in_fixture','position_in_test', 'position_in_routes'
...@@ -75,7 +69,10 @@ OptionParser.new do |opts| ...@@ -75,7 +69,10 @@ OptionParser.new do |opts|
opts.on('-r', '--routes', opts.on('-r', '--routes',
"Annotate routes.rb with the output of 'rake routes'") do "Annotate routes.rb with the output of 'rake routes'") do
task = :annotate_routes target = {
:klass => AnnotateRoutes,
:task => :do_annotations
}
end end
opts.on('-v', '--version', opts.on('-v', '--version',
...@@ -139,9 +136,7 @@ OptionParser.new do |opts| ...@@ -139,9 +136,7 @@ OptionParser.new do |opts|
end end
end.parse! end.parse!
ENV['is_cli'] = '1'
if Annotate.load_tasks options=Annotate.setup_options({ :is_rake => !ENV['is_rake'].blank? })
Rake::Task[task].invoke Annotate.eager_load(options)
else target[:klass].send(target[:task], options)
STDERR.puts "Can't find Rakefile. Are we in a Rails folder?"
end
$:.unshift(File.dirname(__FILE__)) $:.unshift(File.dirname(__FILE__))
require 'annotate/version' require 'annotate/version'
require 'annotate/annotate_models'
require 'annotate/annotate_routes'
begin
# ActiveSupport 3.x...
require 'active_support/hash_with_indifferent_access'
rescue Exception => e
# ActiveSupport 2.x...
require 'active_support/core_ext/hash/indifferent_access'
end
module Annotate module Annotate
##
# The set of available options to customize the behavior of Annotate.
#
POSITION_OPTIONS=[
:position_in_routes, :position_in_class, :position_in_test,
:position_in_fixture, :position_in_factory, :position,
]
FLAG_OPTIONS=[
:show_indexes, :simple_indexes, :include_version, :exclude_tests,
:exclude_fixtures, :exclude_factories, :ignore_model_sub_dir,
:format_bare, :format_rdoc, :format_markdown, :sort, :force, :trace,
]
OTHER_OPTIONS=[
:model_dir,
]
PATH_OPTIONS=[
:require,
]
##
# Set default values that can be overridden via environment variables.
#
def self.set_defaults(options = {})
return if(@has_set_defaults)
@has_set_defaults = true
options = HashWithIndifferentAccess.new(options)
[POSITION_OPTIONS, FLAG_OPTIONS, PATH_OPTIONS].flatten.each do |key|
if(options.has_key?(key))
default_value = if(options[key].is_a?(Array))
options[key].join(",")
else
options[key]
end
end
default_value = ENV[key.to_s] if(!ENV[key.to_s].blank?)
ENV[key.to_s] = default_value.to_s
end
end
TRUE_RE = /^(true|t|yes|y|1)$/i
def self.setup_options(options = {})
POSITION_OPTIONS.each do |key|
options[key] = fallback(ENV[key.to_s], ENV['position'], 'before')
end
FLAG_OPTIONS.each do |key|
options[key] = true?(ENV[key.to_s])
end
OTHER_OPTIONS.each do |key|
options[key] = (!ENV[key.to_s].blank?) ? ENV[key.to_s] : nil
end
PATH_OPTIONS.each do |key|
options[key] = (!ENV[key.to_s].blank?) ? ENV[key.to_s].split(',') : []
end
if(!options[:model_dir])
options[:model_dir] = 'app/models'
end
return options
end
def self.skip_on_migration?
ENV['skip_on_db_migrate'] =~ TRUE_RE
end
def self.loaded_tasks=(val); @loaded_tasks = val; end def self.loaded_tasks=(val); @loaded_tasks = val; end
def self.loaded_tasks; return @loaded_tasks; end def self.loaded_tasks; return @loaded_tasks; end
def self.load_tasks def self.load_tasks
if File.exists?('Rakefile')
return if(self.loaded_tasks) return if(self.loaded_tasks)
self.loaded_tasks = true self.loaded_tasks = true
require 'rake'
load './Rakefile'
Dir[File.join(File.dirname(__FILE__), 'tasks', '**/*.rake')].each { |rake| load rake } Dir[File.join(File.dirname(__FILE__), 'tasks', '**/*.rake')].each { |rake| load rake }
return true end
def self.load_requires(options)
options[:require].each { |path| require path } if options[:require].count > 0
end
def self.eager_load(options)
self.load_requires(options)
require "annotate/active_record_patch"
if(defined?(Rails))
if(Rails.version.split('.').first.to_i < 3)
Rails.configuration.eager_load_paths.each do |load_path|
matcher = /\A#{Regexp.escape(load_path)}(.*)\.rb\Z/
Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
require_dependency file.sub(matcher, '\1')
end
end
else else
return false klass = Rails::Application.send(:subclasses).first
klass.eager_load!
end
else
FileList["#{options[:model_dir]}/**/*.rb"].each do |fname|
require File.expand_path(fname)
end
end end
end end
def self.bootstrap_rake
begin
require 'rake/dsl_definition'
rescue Exception => e
# We might just be on an old version of Rake...
end
require 'rake'
if File.exists?('./Rakefile')
load './Rakefile'
end
Rake::Task[:environment].invoke rescue nil
if(!defined?(Rails))
# Not in a Rails project, so time to load up the parts of
# ActiveSupport we need.
require 'active_support'
require 'active_support/core_ext/class/subclasses'
require 'active_support/core_ext/string/inflections'
end
self.load_tasks
Rake::Task[:set_annotation_options].invoke
end
def self.fallback(*args) def self.fallback(*args)
return args.detect { |arg| !arg.blank? } return args.detect { |arg| !arg.blank? }
end end
...@@ -29,8 +147,4 @@ module Annotate ...@@ -29,8 +147,4 @@ module Annotate
return false unless(val =~ TRUE_RE) return false unless(val =~ TRUE_RE)
return true return true
end end
private
TRUE_RE = /^(true|t|yes|y|1)$/i
end end
...@@ -198,7 +198,7 @@ module AnnotateModels ...@@ -198,7 +198,7 @@ module AnnotateModels
# :position_in_*<Symbol>:: where to place the annotated section in fixture or model file, # :position_in_*<Symbol>:: where to place the annotated section in fixture or model file,
# :before or :after. Default is :before. # :before or :after. Default is :before.
# #
def annotate_one_file(file_name, info_block, options={}) def annotate_one_file(file_name, info_block, position, options={})
if File.exist?(file_name) if File.exist?(file_name)
old_content = File.read(file_name) old_content = File.read(file_name)
return false if(old_content =~ /# -\*- SkipSchemaAnnotations.*\n/) return false if(old_content =~ /# -\*- SkipSchemaAnnotations.*\n/)
...@@ -236,7 +236,7 @@ module AnnotateModels ...@@ -236,7 +236,7 @@ module AnnotateModels
old_content.sub!(encoding, '') old_content.sub!(encoding, '')
old_content.sub!(PATTERN, '') old_content.sub!(PATTERN, '')
new_content = (options[:position] || 'before').to_s == 'after' ? new_content = options[position].to_s == 'after' ?
(encoding_header + (old_content.rstrip + "\n\n" + info_block)) : (encoding_header + (old_content.rstrip + "\n\n" + info_block)) :
(encoding_header + info_block + old_content) (encoding_header + info_block + old_content)
...@@ -383,12 +383,6 @@ module AnnotateModels ...@@ -383,12 +383,6 @@ module AnnotateModels
# if its a subclass of ActiveRecord::Base, # if its a subclass of ActiveRecord::Base,
# then pass it to the associated block # then pass it to the associated block
def do_annotations(options={}) def do_annotations(options={})
if options[:require]
options[:require].each do |path|
require path
end
end
header = options[:format_markdown] ? PREFIX_MD.dup : PREFIX.dup header = options[:format_markdown] ? PREFIX_MD.dup : PREFIX.dup
if options[:include_version] if options[:include_version]
...@@ -426,11 +420,6 @@ module AnnotateModels ...@@ -426,11 +420,6 @@ module AnnotateModels
end end
def remove_annotations(options={}) def remove_annotations(options={})
if options[:require]
options[:require].each do |path|
require path
end
end
self.model_dir = options[:model_dir] if options[:model_dir] self.model_dir = options[:model_dir] if options[:model_dir]
deannotated = [] deannotated = []
...@@ -439,9 +428,8 @@ module AnnotateModels ...@@ -439,9 +428,8 @@ module AnnotateModels
begin begin
klass = get_model_class(file) klass = get_model_class(file)
if klass < ActiveRecord::Base && !klass.abstract_class? if klass < ActiveRecord::Base && !klass.abstract_class?
deannotated << klass
model_name = klass.name.underscore model_name = klass.name.underscore
table_name = klass.table_name
model_file_name = File.join(model_dir, file) model_file_name = File.join(model_dir, file)
deannotated_klass = true if(remove_annotation_of_file(model_file_name)) deannotated_klass = true if(remove_annotation_of_file(model_file_name))
...@@ -462,9 +450,8 @@ module AnnotateModels ...@@ -462,9 +450,8 @@ module AnnotateModels
deannotated_klass = true deannotated_klass = true
end end
end end
deannotated << klass if(deannotated_klass)
end end
deannotated << klass if(deannotated_klass)
rescue Exception => e rescue Exception => e
puts "Unable to deannotate #{file}: #{e.message}" puts "Unable to deannotate #{file}: #{e.message}"
puts "\t" + e.backtrace.join("\n\t") if options[:trace] puts "\t" + e.backtrace.join("\n\t") if options[:trace]
......
...@@ -23,12 +23,6 @@ module AnnotateRoutes ...@@ -23,12 +23,6 @@ module AnnotateRoutes
def self.do_annotations(options={}) def self.do_annotations(options={})
return unless(routes_exists?) return unless(routes_exists?)
if options[:require]
options[:require].each do |path|
require path
end
end
position_after = options[:position_in_routes] != 'before' position_after = options[:position_in_routes] != 'before'
routes_map = `rake routes`.split(/\n/, -1) routes_map = `rake routes`.split(/\n/, -1)
...@@ -74,12 +68,6 @@ module AnnotateRoutes ...@@ -74,12 +68,6 @@ module AnnotateRoutes
def self.remove_annotations(options={}) def self.remove_annotations(options={})
return unless(routes_exists?) return unless(routes_exists?)
if options[:require]
options[:require].each do |path|
require path
end
end
(content, where_header_found) = strip_annotations(File.read(routes_file)) (content, where_header_found) = strip_annotations(File.read(routes_file))
content = strip_on_removal(content, where_header_found) content = strip_on_removal(content, where_header_found)
......
Add a .rake file that automatically annotates models when you do a db:migrate Add a .rake file that automatically annotates models when you do a db:migrate
in development mode: in development mode:
rails generate annotate_models:install rails generate annotate:install
module AnnotateModels module Annotate
module Generators module Generators
class InstallGenerator < Rails::Generators::Base class InstallGenerator < Rails::Generators::Base
desc "Copy annotate_models rakefiles for automatic annotation" desc "Copy annotate_models rakefiles for automatic annotation"
......
# NOTE: only doing this in development as some production environments (Heroku)
# NOTE: are sensitive to local FS writes, and besides -- it's just not proper
# NOTE: to have a dev-mode tool do its thing in production.
if(Rails.env.development?)
task :set_annotation_options do
# You can override any of these by setting an environment variable of the
# same name.
Annotate.set_defaults({
'position_in_routes' => "before",
'position_in_class' => "before",
'position_in_test' => "before",
'position_in_fixture' => "before",
'position_in_factory' => "before",
'show_indexes' => "true",
'simple_indexes' => "false",
'model_dir' => "app/models",
'include_version' => "false",
'require' => "",
'exclude_tests' => "false",
'exclude_fixtures' => "false",
'exclude_factories' => "false",
'ignore_model_sub_dir' => "false",
'skip_on_db_migrate' => "false",
'format_bare' => "true",
'format_rdoc' => "false",
'format_markdown' => "false",
'sort' => "false",
'force' => "false",
'trace' => "false",
})
end
Annotate.load_tasks
end
# NOTE: only doing this in development as some production environments (Heroku)
# NOTE: are sensitive to local FS writes, and besides -- it's just not proper
# NOTE: to have a dev-mode tool do its thing in production.
if(Rails.env.development?)
task :set_annotation_options do
ENV['position_in_class'] = "before"
ENV['position_in_fixture'] = "before"
ENV['position_in_factory'] = "before"
ENV['show_indexes'] = "true"
ENV['include_version'] = "false"
ENV['exclude_tests'] = "false"
ENV['exclude_fixtures'] = "false"
ENV['ignore_model_sub_dir'] = "false"
ENV['skip_on_db_migrate'] = "false"
ENV['format_rdoc'] = "false"
ENV['format_markdown'] = "false"
ENV['no_sort'] = "false"
ENV['force'] = "false"
end
end
...@@ -315,7 +315,15 @@ end ...@@ -315,7 +315,15 @@ end
end end
def annotate_one_file options = {} def annotate_one_file options = {}
AnnotateModels.annotate_one_file(@model_file_name, @schema_info, options) Annotate.set_defaults(options)
options = Annotate.setup_options(options)
AnnotateModels.annotate_one_file(@model_file_name, @schema_info, :position_in_class, options)
# Wipe settings so the next call will pick up new values...
Annotate.instance_variable_set('@has_set_defaults', false)
Annotate::POSITION_OPTIONS.each { |key| ENV[key.to_s] = '' }
Annotate::FLAG_OPTIONS.each { |key| ENV[key.to_s] = '' }
Annotate::PATH_OPTIONS.each { |key| ENV[key.to_s] = '' }
end end
it "should annotate the file before the model if position == 'before'" do it "should annotate the file before the model if position == 'before'" do
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment