Commit ebc1b037 by Jon Frisby

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

parent 8e070848
== 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
of the `kramdown` engine for parsing it.
* Improved Markdown formatting considerably.
......@@ -49,7 +51,8 @@
* Allow task loading from Rakefile for gems (plugin installation already
auto-detects).
* 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
* Leave magic encoding comment intact
* Fix issue #14 - RuntimeError: Already memoized
......
......@@ -10,7 +10,7 @@ your...
- Machinist blueprints
- Fabrication fabricators
- 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:
......@@ -69,6 +69,8 @@ Into environment gems from Github checkout:
(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:
cd /path/to/app
......@@ -97,6 +99,18 @@ To remove routes.rb annotations:
== 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
anywhere in the file:
......@@ -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.
=== 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
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
Usage: annotate [options] [model_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]
Place the annotations at the top (before) or the bottom (after) of the model file
--pf, --position-in-factory [before|after]
......
......@@ -10,31 +10,25 @@ here = File.expand_path(File.dirname __FILE__)
$:<< "#{here}/../lib"
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'
Annotate.bootstrap_rake
task = :annotate_models
target = {
:klass => AnnotateModels,
:task => :do_annotations,
}
has_set_position = {}
OptionParser.new do |opts|
opts.banner = "Usage: annotate [options] [model_file]*"
opts.on('-d', '--delete',
"Remove annotations from all model files or the routes.rb file") do
if(task == :annotate_routes)
task = :remove_routes
else
task = :remove_annotation
end
target[:task] = :remove_annotations
end
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
[
'position_in_class','position_in_factory','position_in_fixture','position_in_test', 'position_in_routes'
......@@ -75,7 +69,10 @@ OptionParser.new do |opts|
opts.on('-r', '--routes',
"Annotate routes.rb with the output of 'rake routes'") do
task = :annotate_routes
target = {
:klass => AnnotateRoutes,
:task => :do_annotations
}
end
opts.on('-v', '--version',
......@@ -139,9 +136,7 @@ OptionParser.new do |opts|
end
end.parse!
ENV['is_cli'] = '1'
if Annotate.load_tasks
Rake::Task[task].invoke
else
STDERR.puts "Can't find Rakefile. Are we in a Rails folder?"
end
options=Annotate.setup_options({ :is_rake => !ENV['is_rake'].blank? })
Annotate.eager_load(options)
target[:klass].send(target[:task], options)
$:.unshift(File.dirname(__FILE__))
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
##
# 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; return @loaded_tasks; end
def self.load_tasks
if File.exists?('Rakefile')
return if(self.loaded_tasks)
self.loaded_tasks = true
require 'rake'
load './Rakefile'
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
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
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)
return args.detect { |arg| !arg.blank? }
end
......@@ -29,8 +147,4 @@ module Annotate
return false unless(val =~ TRUE_RE)
return true
end
private
TRUE_RE = /^(true|t|yes|y|1)$/i
end
......@@ -198,7 +198,7 @@ module AnnotateModels
# :position_in_*<Symbol>:: where to place the annotated section in fixture or model file,
# :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)
old_content = File.read(file_name)
return false if(old_content =~ /# -\*- SkipSchemaAnnotations.*\n/)
......@@ -236,7 +236,7 @@ module AnnotateModels
old_content.sub!(encoding, '')
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 + info_block + old_content)
......@@ -383,12 +383,6 @@ module AnnotateModels
# if its a subclass of ActiveRecord::Base,
# then pass it to the associated block
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
if options[:include_version]
......@@ -426,11 +420,6 @@ module AnnotateModels
end
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]
deannotated = []
......@@ -439,9 +428,8 @@ module AnnotateModels
begin
klass = get_model_class(file)
if klass < ActiveRecord::Base && !klass.abstract_class?
deannotated << klass
model_name = klass.name.underscore
table_name = klass.table_name
model_file_name = File.join(model_dir, file)
deannotated_klass = true if(remove_annotation_of_file(model_file_name))
......@@ -462,9 +450,8 @@ module AnnotateModels
deannotated_klass = true
end
end
deannotated << klass if(deannotated_klass)
end
deannotated << klass if(deannotated_klass)
rescue Exception => e
puts "Unable to deannotate #{file}: #{e.message}"
puts "\t" + e.backtrace.join("\n\t") if options[:trace]
......
......@@ -23,12 +23,6 @@ module AnnotateRoutes
def self.do_annotations(options={})
return unless(routes_exists?)
if options[:require]
options[:require].each do |path|
require path
end
end
position_after = options[:position_in_routes] != 'before'
routes_map = `rake routes`.split(/\n/, -1)
......@@ -74,12 +68,6 @@ module AnnotateRoutes
def self.remove_annotations(options={})
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 = strip_on_removal(content, where_header_found)
......
Add a .rake file that automatically annotates models when you do a db:migrate
in development mode:
rails generate annotate_models:install
rails generate annotate:install
module AnnotateModels
module Annotate
module Generators
class InstallGenerator < Rails::Generators::Base
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
end
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
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