Skip to content

Commit ebc1b03

Browse files
committed
Overhaul options handling, and also code loading to allow standalone usage again.
1 parent 8e07084 commit ebc1b03

File tree

11 files changed

+220
-88
lines changed

11 files changed

+220
-88
lines changed

CHANGELOG.rdoc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
== 2.5.0
22

3+
* It's now possible to use Annotate in standalone ActiveRecord (non-Rails)
4+
projects again.
35
* Adding note that Markdown is actually MultiMarkdown, and recommending the use
46
of the `kramdown` engine for parsing it.
57
* Improved Markdown formatting considerably.
@@ -49,7 +51,8 @@
4951
* Allow task loading from Rakefile for gems (plugin installation already
5052
auto-detects).
5153
* Add skip_on_db_migrate option as well for people that don't want it
52-
* Fix options parsing to convert strings to proper booleans
54+
* Fix options parsing to convert strings to proper booleans. Change annotate to
55+
use options hash instead of ENV.
5356
* Add support for Fabrication fabricators
5457
* Leave magic encoding comment intact
5558
* Fix issue #14 - RuntimeError: Already memoized

README.rdoc

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ your...
1010
- Machinist blueprints
1111
- Fabrication fabricators
1212
- Thoughtbot's factory_girl factories, i.e. the (spec|test)/factories/<model>_factory.rb files
13-
- routes.rb file
13+
- routes.rb file (for Rails projects)
1414

1515
The schema comment looks like this:
1616

@@ -69,6 +69,8 @@ Into environment gems from Github checkout:
6969

7070
(If you used the Gemfile install, prefix the below commands with `bundle exec`.)
7171

72+
=== Usage in Rails
73+
7274
To annotate all your models, tests, fixtures, and factories:
7375

7476
cd /path/to/app
@@ -95,6 +97,18 @@ To remove routes.rb annotations:
9597
annotate --routes --delete
9698

9799

100+
== Configuration
101+
102+
103+
=== Usage Outside of Rails
104+
105+
Everything above applies, except that --routes is not meaningful, and you will
106+
probably need to explicitly set one or more `--require` option(s), and/or one
107+
or more `--model-dir` options to inform annotate about the structure of your
108+
project and help it bootstrap and load the relevant code.
109+
110+
111+
98112
== Configuration
99113

100114
If you want to always skip annotations on a particular model, add this string
@@ -111,6 +125,15 @@ Edit this file to control things like output format, where annotations are
111125
added (top or bottom of file), and in which artifacts.
112126

113127

128+
=== Configuration in Rails
129+
130+
To generate a configuration file (in the form of a `.rake` file), to set
131+
default options:
132+
133+
rails g annotate:install
134+
135+
Edit this file to control things like output format, where annotations are
136+
added (top or bottom of file), and in which artifacts.
114137
== Rails Integration
115138

116139
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
135158

136159
Usage: annotate [options] [model_file]*
137160
-d, --delete Remove annotations from all model files or the routes.rb file
138-
-p, --position [before|after] Place the annotations at the top (before) or the bottom (after) of the model/test/fixture/factory file(s)
161+
-p, --position [before|after] Place the annotations at the top (before) or the bottom (after) of the model/test/fixture/factory/routes file(s)
139162
--pc, --position-in-class [before|after]
140163
Place the annotations at the top (before) or the bottom (after) of the model file
141164
--pf, --position-in-factory [before|after]

bin/annotate

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,25 @@ here = File.expand_path(File.dirname __FILE__)
1010
$:<< "#{here}/../lib"
1111

1212
require 'optparse'
13-
begin
14-
require 'rake/dsl_definition'
15-
rescue Exception => e
16-
# We might just be on an old version of Rake...
17-
end
18-
require 'rake'
1913
require 'annotate'
14+
Annotate.bootstrap_rake
2015

21-
task = :annotate_models
22-
16+
target = {
17+
:klass => AnnotateModels,
18+
:task => :do_annotations,
19+
}
2320
has_set_position = {}
2421
OptionParser.new do |opts|
2522
opts.banner = "Usage: annotate [options] [model_file]*"
2623

2724
opts.on('-d', '--delete',
2825
"Remove annotations from all model files or the routes.rb file") do
29-
if(task == :annotate_routes)
30-
task = :remove_routes
31-
else
32-
task = :remove_annotation
33-
end
26+
27+
target[:task] = :remove_annotations
3428
end
3529

3630
opts.on('-p', '--position [before|after]', ['before', 'after'],
37-
"Place the annotations at the top (before) or the bottom (after) of the model/test/fixture/factory file(s)") do |p|
31+
"Place the annotations at the top (before) or the bottom (after) of the model/test/fixture/factory/routes file(s)") do |p|
3832
ENV['position'] = p
3933
[
4034
'position_in_class','position_in_factory','position_in_fixture','position_in_test', 'position_in_routes'
@@ -75,7 +69,10 @@ OptionParser.new do |opts|
7569

7670
opts.on('-r', '--routes',
7771
"Annotate routes.rb with the output of 'rake routes'") do
78-
task = :annotate_routes
72+
target = {
73+
:klass => AnnotateRoutes,
74+
:task => :do_annotations
75+
}
7976
end
8077

8178
opts.on('-v', '--version',
@@ -139,9 +136,7 @@ OptionParser.new do |opts|
139136
end
140137
end.parse!
141138

142-
ENV['is_cli'] = '1'
143-
if Annotate.load_tasks
144-
Rake::Task[task].invoke
145-
else
146-
STDERR.puts "Can't find Rakefile. Are we in a Rails folder?"
147-
end
139+
140+
options=Annotate.setup_options({ :is_rake => !ENV['is_rake'].blank? })
141+
Annotate.eager_load(options)
142+
target[:klass].send(target[:task], options)

lib/annotate.rb

Lines changed: 126 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,143 @@
11
$:.unshift(File.dirname(__FILE__))
22
require 'annotate/version'
3+
require 'annotate/annotate_models'
4+
require 'annotate/annotate_routes'
5+
6+
begin
7+
# ActiveSupport 3.x...
8+
require 'active_support/hash_with_indifferent_access'
9+
rescue Exception => e
10+
# ActiveSupport 2.x...
11+
require 'active_support/core_ext/hash/indifferent_access'
12+
end
313

414
module Annotate
15+
##
16+
# The set of available options to customize the behavior of Annotate.
17+
#
18+
POSITION_OPTIONS=[
19+
:position_in_routes, :position_in_class, :position_in_test,
20+
:position_in_fixture, :position_in_factory, :position,
21+
]
22+
FLAG_OPTIONS=[
23+
:show_indexes, :simple_indexes, :include_version, :exclude_tests,
24+
:exclude_fixtures, :exclude_factories, :ignore_model_sub_dir,
25+
:format_bare, :format_rdoc, :format_markdown, :sort, :force, :trace,
26+
]
27+
OTHER_OPTIONS=[
28+
:model_dir,
29+
]
30+
PATH_OPTIONS=[
31+
:require,
32+
]
33+
34+
35+
##
36+
# Set default values that can be overridden via environment variables.
37+
#
38+
def self.set_defaults(options = {})
39+
return if(@has_set_defaults)
40+
@has_set_defaults = true
41+
options = HashWithIndifferentAccess.new(options)
42+
[POSITION_OPTIONS, FLAG_OPTIONS, PATH_OPTIONS].flatten.each do |key|
43+
if(options.has_key?(key))
44+
default_value = if(options[key].is_a?(Array))
45+
options[key].join(",")
46+
else
47+
options[key]
48+
end
49+
end
50+
default_value = ENV[key.to_s] if(!ENV[key.to_s].blank?)
51+
ENV[key.to_s] = default_value.to_s
52+
end
53+
end
54+
55+
TRUE_RE = /^(true|t|yes|y|1)$/i
56+
def self.setup_options(options = {})
57+
POSITION_OPTIONS.each do |key|
58+
options[key] = fallback(ENV[key.to_s], ENV['position'], 'before')
59+
end
60+
FLAG_OPTIONS.each do |key|
61+
options[key] = true?(ENV[key.to_s])
62+
end
63+
OTHER_OPTIONS.each do |key|
64+
options[key] = (!ENV[key.to_s].blank?) ? ENV[key.to_s] : nil
65+
end
66+
PATH_OPTIONS.each do |key|
67+
options[key] = (!ENV[key.to_s].blank?) ? ENV[key.to_s].split(',') : []
68+
end
69+
70+
if(!options[:model_dir])
71+
options[:model_dir] = 'app/models'
72+
end
73+
74+
return options
75+
end
76+
77+
def self.skip_on_migration?
78+
ENV['skip_on_db_migrate'] =~ TRUE_RE
79+
end
80+
581
def self.loaded_tasks=(val); @loaded_tasks = val; end
682
def self.loaded_tasks; return @loaded_tasks; end
783

884
def self.load_tasks
9-
if File.exists?('Rakefile')
10-
return if(self.loaded_tasks)
11-
self.loaded_tasks = true
85+
return if(self.loaded_tasks)
86+
self.loaded_tasks = true
1287

13-
require 'rake'
14-
load './Rakefile'
88+
Dir[File.join(File.dirname(__FILE__), 'tasks', '**/*.rake')].each { |rake| load rake }
89+
end
1590

16-
Dir[File.join(File.dirname(__FILE__), 'tasks', '**/*.rake')].each { |rake| load rake }
17-
return true
91+
def self.load_requires(options)
92+
options[:require].each { |path| require path } if options[:require].count > 0
93+
end
94+
95+
def self.eager_load(options)
96+
self.load_requires(options)
97+
require "annotate/active_record_patch"
98+
99+
if(defined?(Rails))
100+
if(Rails.version.split('.').first.to_i < 3)
101+
Rails.configuration.eager_load_paths.each do |load_path|
102+
matcher = /\A#{Regexp.escape(load_path)}(.*)\.rb\Z/
103+
Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
104+
require_dependency file.sub(matcher, '\1')
105+
end
106+
end
107+
else
108+
klass = Rails::Application.send(:subclasses).first
109+
klass.eager_load!
110+
end
18111
else
19-
return false
112+
FileList["#{options[:model_dir]}/**/*.rb"].each do |fname|
113+
require File.expand_path(fname)
114+
end
20115
end
21116
end
22117

118+
def self.bootstrap_rake
119+
begin
120+
require 'rake/dsl_definition'
121+
rescue Exception => e
122+
# We might just be on an old version of Rake...
123+
end
124+
require 'rake'
125+
126+
if File.exists?('./Rakefile')
127+
load './Rakefile'
128+
end
129+
Rake::Task[:environment].invoke rescue nil
130+
if(!defined?(Rails))
131+
# Not in a Rails project, so time to load up the parts of
132+
# ActiveSupport we need.
133+
require 'active_support'
134+
require 'active_support/core_ext/class/subclasses'
135+
require 'active_support/core_ext/string/inflections'
136+
end
137+
self.load_tasks
138+
Rake::Task[:set_annotation_options].invoke
139+
end
140+
23141
def self.fallback(*args)
24142
return args.detect { |arg| !arg.blank? }
25143
end
@@ -29,8 +147,4 @@ def self.true?(val)
29147
return false unless(val =~ TRUE_RE)
30148
return true
31149
end
32-
33-
private
34-
35-
TRUE_RE = /^(true|t|yes|y|1)$/i
36150
end

lib/annotate/annotate_models.rb

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ def get_index_info(klass, options={})
198198
# :position_in_*<Symbol>:: where to place the annotated section in fixture or model file,
199199
# :before or :after. Default is :before.
200200
#
201-
def annotate_one_file(file_name, info_block, options={})
201+
def annotate_one_file(file_name, info_block, position, options={})
202202
if File.exist?(file_name)
203203
old_content = File.read(file_name)
204204
return false if(old_content =~ /# -\*- SkipSchemaAnnotations.*\n/)
@@ -236,7 +236,7 @@ def annotate_one_file(file_name, info_block, options={})
236236
old_content.sub!(encoding, '')
237237
old_content.sub!(PATTERN, '')
238238

239-
new_content = (options[:position] || 'before').to_s == 'after' ?
239+
new_content = options[position].to_s == 'after' ?
240240
(encoding_header + (old_content.rstrip + "\n\n" + info_block)) :
241241
(encoding_header + info_block + old_content)
242242

@@ -383,12 +383,6 @@ def get_loaded_model(model_path)
383383
# if its a subclass of ActiveRecord::Base,
384384
# then pass it to the associated block
385385
def do_annotations(options={})
386-
if options[:require]
387-
options[:require].each do |path|
388-
require path
389-
end
390-
end
391-
392386
header = options[:format_markdown] ? PREFIX_MD.dup : PREFIX.dup
393387

394388
if options[:include_version]
@@ -426,11 +420,6 @@ def annotate_model_file(annotated, file, header, options)
426420
end
427421

428422
def remove_annotations(options={})
429-
if options[:require]
430-
options[:require].each do |path|
431-
require path
432-
end
433-
end
434423

435424
self.model_dir = options[:model_dir] if options[:model_dir]
436425
deannotated = []
@@ -439,9 +428,8 @@ def remove_annotations(options={})
439428
begin
440429
klass = get_model_class(file)
441430
if klass < ActiveRecord::Base && !klass.abstract_class?
442-
deannotated << klass
443-
444431
model_name = klass.name.underscore
432+
table_name = klass.table_name
445433
model_file_name = File.join(model_dir, file)
446434
deannotated_klass = true if(remove_annotation_of_file(model_file_name))
447435

@@ -462,9 +450,8 @@ def remove_annotations(options={})
462450
deannotated_klass = true
463451
end
464452
end
465-
466-
deannotated << klass if(deannotated_klass)
467453
end
454+
deannotated << klass if(deannotated_klass)
468455
rescue Exception => e
469456
puts "Unable to deannotate #{file}: #{e.message}"
470457
puts "\t" + e.backtrace.join("\n\t") if options[:trace]

lib/annotate/annotate_routes.rb

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,6 @@ module AnnotateRoutes
2323
def self.do_annotations(options={})
2424
return unless(routes_exists?)
2525

26-
if options[:require]
27-
options[:require].each do |path|
28-
require path
29-
end
30-
end
31-
3226
position_after = options[:position_in_routes] != 'before'
3327

3428
routes_map = `rake routes`.split(/\n/, -1)
@@ -74,12 +68,6 @@ def self.do_annotations(options={})
7468
def self.remove_annotations(options={})
7569
return unless(routes_exists?)
7670

77-
if options[:require]
78-
options[:require].each do |path|
79-
require path
80-
end
81-
end
82-
8371
(content, where_header_found) = strip_annotations(File.read(routes_file))
8472

8573
content = strip_on_removal(content, where_header_found)

0 commit comments

Comments
 (0)