annotate 2.7.4 → 3.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/{AUTHORS.rdoc → AUTHORS.md} +2 -2
- data/CHANGELOG.md +326 -0
- data/{README.rdoc → README.md} +153 -98
- data/RELEASE.md +19 -0
- data/annotate.gemspec +7 -26
- data/bin/annotate +7 -187
- data/lib/annotate.rb +23 -82
- data/lib/annotate/annotate_models.rb +184 -83
- data/lib/annotate/annotate_routes.rb +111 -167
- data/lib/annotate/annotate_routes/helpers.rb +69 -0
- data/lib/annotate/constants.rb +38 -0
- data/lib/annotate/helpers.rb +30 -0
- data/lib/annotate/parser.rb +303 -0
- data/lib/annotate/version.rb +1 -1
- data/lib/generators/annotate/templates/auto_annotate_models.rake +45 -40
- data/lib/tasks/annotate_models.rake +37 -33
- data/lib/tasks/annotate_models_migrate.rake +14 -16
- data/lib/tasks/annotate_routes.rake +5 -5
- data/potato.md +41 -0
- metadata +21 -16
- data/CHANGELOG.rdoc +0 -220
- data/TODO.rdoc +0 -11
data/RELEASE.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
## Prerequisite
|
2
|
+
|
3
|
+
- Install "git-flow" (`brew install git-flow`)
|
4
|
+
- Install "bump" gem (`gem install bump`)
|
5
|
+
|
6
|
+
|
7
|
+
## Perform a release
|
8
|
+
|
9
|
+
- `git flow release start <release>`
|
10
|
+
- Update the `CHANGELOG.md` file
|
11
|
+
- `bump current`
|
12
|
+
- `bump patch`
|
13
|
+
- `rm -rf dist`
|
14
|
+
- `rake spec`
|
15
|
+
- `rake gem`
|
16
|
+
- `git flow release finish <release>`
|
17
|
+
|
18
|
+
- `rake gem:publish`
|
19
|
+
|
data/annotate.gemspec
CHANGED
@@ -7,42 +7,23 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.name = 'annotate'
|
8
8
|
s.version = Annotate.version
|
9
9
|
|
10
|
-
s.required_ruby_version = '>= 2.
|
10
|
+
s.required_ruby_version = '>= 2.4.0'
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
|
12
12
|
s.authors = ['Alex Chaffee', 'Cuong Tran', 'Marcos Piccinini', 'Turadg Aleahmad', 'Jon Frisby']
|
13
13
|
s.description = 'Annotates Rails/ActiveRecord Models, routes, fixtures, and others based on the database schema.'
|
14
14
|
s.email = ['alex@stinky.com', 'cuong.tran@gmail.com', 'x@nofxx.com', 'turadg@aleahmad.net', 'jon@cloudability.com']
|
15
15
|
s.executables = ['annotate']
|
16
|
-
s.extra_rdoc_files = ['README.
|
17
|
-
s.files =
|
18
|
-
'AUTHORS.rdoc',
|
19
|
-
'CHANGELOG.rdoc',
|
20
|
-
'LICENSE.txt',
|
21
|
-
'README.rdoc',
|
22
|
-
'TODO.rdoc',
|
23
|
-
'annotate.gemspec',
|
24
|
-
'bin/annotate',
|
25
|
-
'lib/annotate.rb',
|
26
|
-
'lib/annotate/active_record_patch.rb',
|
27
|
-
'lib/annotate/annotate_models.rb',
|
28
|
-
'lib/annotate/annotate_routes.rb',
|
29
|
-
'lib/annotate/tasks.rb',
|
30
|
-
'lib/annotate/version.rb',
|
31
|
-
'lib/generators/annotate/USAGE',
|
32
|
-
'lib/generators/annotate/install_generator.rb',
|
33
|
-
'lib/generators/annotate/templates/auto_annotate_models.rake',
|
34
|
-
'lib/tasks/annotate_models.rake',
|
35
|
-
'lib/tasks/annotate_routes.rake',
|
36
|
-
'lib/tasks/annotate_models_migrate.rake'
|
37
|
-
]
|
16
|
+
s.extra_rdoc_files = ['README.md', 'CHANGELOG.md']
|
17
|
+
s.files = `git ls-files -z LICENSE.txt *.md *.gemspec bin lib`.split("\x0")
|
38
18
|
s.homepage = 'http://github.com/ctran/annotate_models'
|
39
19
|
s.licenses = ['Ruby']
|
40
20
|
s.require_paths = ['lib']
|
41
|
-
s.rubyforge_project = 'annotate'
|
42
21
|
s.rubygems_version = '2.1.11'
|
43
22
|
s.summary = 'Annotates Rails Models, routes, fixtures, and others based on the database schema.'
|
44
23
|
|
45
24
|
s.specification_version = 4 if s.respond_to? :specification_version
|
46
|
-
s.add_runtime_dependency(%q<rake>,
|
47
|
-
s.add_runtime_dependency(%q<activerecord>, ['>= 3.2', '<
|
25
|
+
s.add_runtime_dependency(%q<rake>, '>= 10.4', '< 14.0')
|
26
|
+
s.add_runtime_dependency(%q<activerecord>, ['>= 3.2', '< 7.0'])
|
27
|
+
|
28
|
+
s.metadata = { "github_repo" => "ssh://github.com/ctran/annotate_models" }
|
48
29
|
end
|
data/bin/annotate
CHANGED
@@ -14,199 +14,19 @@ end
|
|
14
14
|
here = File.expand_path(File.dirname __FILE__)
|
15
15
|
$LOAD_PATH << "#{here}/../lib"
|
16
16
|
|
17
|
-
require 'optparse'
|
18
17
|
require 'annotate'
|
19
|
-
|
20
|
-
|
21
|
-
has_set_position = {}
|
22
|
-
target_action = :do_annotations
|
23
|
-
positions = %w(before top after bottom)
|
24
|
-
|
25
|
-
OptionParser.new do |opts|
|
26
|
-
opts.banner = 'Usage: annotate [options] [model_file]*'
|
27
|
-
|
28
|
-
opts.on('-d', '--delete', 'Remove annotations from all model files or the routes.rb file') do
|
29
|
-
target_action = :remove_annotations
|
30
|
-
end
|
31
|
-
|
32
|
-
opts.on('-p', '--position [before|top|after|bottom]', positions,
|
33
|
-
'Place the annotations at the top (before) or the bottom (after) of the model/test/fixture/factory/route/serializer file(s)') do |p|
|
34
|
-
ENV['position'] = p
|
35
|
-
%w(position_in_class position_in_factory position_in_fixture position_in_test position_in_routes position_in_serializer).each do |key|
|
36
|
-
ENV[key] = p unless has_set_position[key]
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
opts.on('--pc', '--position-in-class [before|top|after|bottom]', positions,
|
41
|
-
'Place the annotations at the top (before) or the bottom (after) of the model file') do |p|
|
42
|
-
ENV['position_in_class'] = p
|
43
|
-
has_set_position['position_in_class'] = true
|
44
|
-
end
|
45
|
-
|
46
|
-
opts.on('--pf', '--position-in-factory [before|top|after|bottom]', positions,
|
47
|
-
'Place the annotations at the top (before) or the bottom (after) of any factory files') do |p|
|
48
|
-
ENV['position_in_factory'] = p
|
49
|
-
has_set_position['position_in_factory'] = true
|
50
|
-
end
|
51
|
-
|
52
|
-
opts.on('--px', '--position-in-fixture [before|top|after|bottom]', positions,
|
53
|
-
'Place the annotations at the top (before) or the bottom (after) of any fixture files') do |p|
|
54
|
-
ENV['position_in_fixture'] = p
|
55
|
-
has_set_position['position_in_fixture'] = true
|
56
|
-
end
|
57
|
-
|
58
|
-
opts.on('--pt', '--position-in-test [before|top|after|bottom]', positions,
|
59
|
-
'Place the annotations at the top (before) or the bottom (after) of any test files') do |p|
|
60
|
-
ENV['position_in_test'] = p
|
61
|
-
has_set_position['position_in_test'] = true
|
62
|
-
end
|
63
|
-
|
64
|
-
opts.on('--pr', '--position-in-routes [before|top|after|bottom]', positions,
|
65
|
-
'Place the annotations at the top (before) or the bottom (after) of the routes.rb file') do |p|
|
66
|
-
ENV['position_in_routes'] = p
|
67
|
-
has_set_position['position_in_routes'] = true
|
68
|
-
end
|
69
|
-
|
70
|
-
opts.on('--ps', '--position-in-serializer [before|top|after|bottom]', positions,
|
71
|
-
'Place the annotations at the top (before) or the bottom (after) of the serializer files') do |p|
|
72
|
-
ENV['position_in_serializer'] = p
|
73
|
-
has_set_position['position_in_serializer'] = true
|
74
|
-
end
|
75
|
-
|
76
|
-
opts.on('--w', '--wrapper STR', 'Wrap annotation with the text passed as parameter.',
|
77
|
-
'If --w option is used, the same text will be used as opening and closing') do |p|
|
78
|
-
ENV['wrapper'] = p
|
79
|
-
end
|
80
|
-
|
81
|
-
opts.on('--wo', '--wrapper-open STR', 'Annotation wrapper opening.') do |p|
|
82
|
-
ENV['wrapper_open'] = p
|
83
|
-
end
|
84
|
-
|
85
|
-
opts.on('--wc', '--wrapper-close STR', 'Annotation wrapper closing') do |p|
|
86
|
-
ENV['wrapper_close'] = p
|
87
|
-
end
|
88
|
-
|
89
|
-
opts.on('-r', '--routes', "Annotate routes.rb with the output of 'rake routes'") do
|
90
|
-
ENV['routes'] = 'true'
|
91
|
-
end
|
92
|
-
|
93
|
-
opts.on('-a', '--active-admin', 'Annotate active_admin models') do
|
94
|
-
ENV['active_admin'] = 'true'
|
95
|
-
end
|
96
|
-
|
97
|
-
opts.on('-v', '--version', 'Show the current version of this gem') do
|
98
|
-
puts "annotate v#{Annotate.version}"; exit
|
99
|
-
end
|
100
|
-
|
101
|
-
opts.on('-m', '--show-migration', 'Include the migration version number in the annotation') do
|
102
|
-
ENV['include_version'] = 'yes'
|
103
|
-
end
|
104
|
-
|
105
|
-
opts.on('-k', '--show-foreign-keys',
|
106
|
-
"List the table's foreign key constraints in the annotation") do
|
107
|
-
ENV['show_foreign_keys'] = 'yes'
|
108
|
-
end
|
18
|
+
require 'annotate/parser'
|
109
19
|
|
110
|
-
|
111
|
-
'--complete-foreign-keys', 'Complete foreign key names in the annotation') do
|
112
|
-
ENV['show_foreign_keys'] = 'yes'
|
113
|
-
ENV['show_complete_foreign_keys'] = 'yes'
|
114
|
-
end
|
115
|
-
|
116
|
-
opts.on('-i', '--show-indexes',
|
117
|
-
"List the table's database indexes in the annotation") do
|
118
|
-
ENV['show_indexes'] = 'yes'
|
119
|
-
end
|
120
|
-
|
121
|
-
opts.on('-s', '--simple-indexes',
|
122
|
-
"Concat the column's related indexes in the annotation") do
|
123
|
-
ENV['simple_indexes'] = 'yes'
|
124
|
-
end
|
125
|
-
|
126
|
-
opts.on('--model-dir dir',
|
127
|
-
"Annotate model files stored in dir rather than app/models, separate multiple dirs with commas") do |dir|
|
128
|
-
ENV['model_dir'] = dir
|
129
|
-
end
|
130
|
-
|
131
|
-
opts.on('--root-dir dir',
|
132
|
-
"Annotate files stored within root dir projects, separate multiple dirs with commas") do |dir|
|
133
|
-
ENV['root_dir'] = dir
|
134
|
-
end
|
135
|
-
|
136
|
-
opts.on('--ignore-model-subdirects',
|
137
|
-
"Ignore subdirectories of the models directory") do |dir|
|
138
|
-
ENV['ignore_model_sub_dir'] = 'yes'
|
139
|
-
end
|
140
|
-
|
141
|
-
opts.on('--sort',
|
142
|
-
"Sort columns alphabetically, rather than in creation order") do |dir|
|
143
|
-
ENV['sort'] = 'yes'
|
144
|
-
end
|
145
|
-
|
146
|
-
opts.on('--classified-sort',
|
147
|
-
"Sort columns alphabetically, but first goes id, then the rest columns, then the timestamp columns and then the association columns") do |dir|
|
148
|
-
ENV['classified_sort'] = 'yes'
|
149
|
-
end
|
150
|
-
|
151
|
-
opts.on('-R', '--require path',
|
152
|
-
"Additional file to require before loading models, may be used multiple times") do |path|
|
153
|
-
if !ENV['require'].blank?
|
154
|
-
ENV['require'] = ENV['require'] + ",#{path}"
|
155
|
-
else
|
156
|
-
ENV['require'] = path
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
opts.on('-e', '--exclude [tests,fixtures,factories,serializers]', Array, "Do not annotate fixtures, test files, factories, and/or serializers") do |exclusions|
|
161
|
-
exclusions ||= %w(tests fixtures factories)
|
162
|
-
exclusions.each { |exclusion| ENV["exclude_#{exclusion}"] = 'yes' }
|
163
|
-
end
|
164
|
-
|
165
|
-
opts.on('-f', '--format [bare|rdoc|markdown]', %w(bare rdoc markdown), 'Render Schema Infomation as plain/RDoc/Markdown') do |fmt|
|
166
|
-
ENV["format_#{fmt}"] = 'yes'
|
167
|
-
end
|
168
|
-
|
169
|
-
opts.on('--force', 'Force new annotations even if there are no changes.') do |force|
|
170
|
-
ENV['force'] = 'yes'
|
171
|
-
end
|
172
|
-
|
173
|
-
opts.on('--timestamp', 'Include timestamp in (routes) annotation') do
|
174
|
-
ENV['timestamp'] = 'true'
|
175
|
-
end
|
176
|
-
|
177
|
-
opts.on('--trace', 'If unable to annotate a file, print the full stack trace, not just the exception message.') do |value|
|
178
|
-
ENV['trace'] = 'yes'
|
179
|
-
end
|
180
|
-
|
181
|
-
opts.on('-I', '--ignore-columns REGEX', "don't annotate columns that match a given REGEX (i.e., `annotate -I '^(id|updated_at|created_at)'`") do |regex|
|
182
|
-
ENV['ignore_columns'] = regex
|
183
|
-
end
|
184
|
-
|
185
|
-
opts.on('--ignore-routes REGEX', "don't annotate routes that match a given REGEX (i.e., `annotate -I '(mobile|resque|pghero)'`") do |regex|
|
186
|
-
ENV['ignore_routes'] = regex
|
187
|
-
end
|
188
|
-
|
189
|
-
opts.on('--hide-limit-column-types VALUES', "don't show limit for given column types, separated by commas (i.e., `integer,boolean,text`)") do |values|
|
190
|
-
ENV['hide_limit_column_types'] = "#{values}"
|
191
|
-
end
|
192
|
-
|
193
|
-
opts.on('--hide-default-column-types VALUES', "don't show default for given column types, separated by commas (i.e., `json,jsonb,hstore`)") do |values|
|
194
|
-
ENV['hide_default_column_types'] = "#{values}"
|
195
|
-
end
|
20
|
+
Annotate.bootstrap_rake
|
196
21
|
|
197
|
-
|
198
|
-
ENV['ignore_unknown_models'] = 'true'
|
199
|
-
end
|
22
|
+
options_result = Annotate::Parser.parse(ARGV)
|
200
23
|
|
201
|
-
|
202
|
-
ENV['with_comment'] = 'true'
|
203
|
-
end
|
204
|
-
end.parse!
|
24
|
+
exit if options_result[:exit]
|
205
25
|
|
206
26
|
options = Annotate.setup_options(
|
207
27
|
is_rake: ENV['is_rake'] && !ENV['is_rake'].empty?
|
208
28
|
)
|
209
|
-
Annotate.eager_load(options) if Annotate.include_models?
|
29
|
+
Annotate.eager_load(options) if Annotate::Helpers.include_models?
|
210
30
|
|
211
|
-
AnnotateModels.send(target_action, options) if Annotate.include_models?
|
212
|
-
AnnotateRoutes.send(target_action, options) if Annotate.include_routes?
|
31
|
+
AnnotateModels.send(options_result[:target_action], options) if Annotate::Helpers.include_models?
|
32
|
+
AnnotateRoutes.send(options_result[:target_action], options) if Annotate::Helpers.include_routes?
|
data/lib/annotate.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
# rubocop:disable Metrics/ModuleLength
|
2
|
-
|
3
1
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
4
2
|
require 'annotate/version'
|
5
3
|
require 'annotate/annotate_models'
|
6
4
|
require 'annotate/annotate_routes'
|
5
|
+
require 'annotate/constants'
|
6
|
+
require 'annotate/helpers'
|
7
7
|
|
8
8
|
begin
|
9
9
|
# ActiveSupport 3.x...
|
@@ -16,38 +16,6 @@ rescue StandardError
|
|
16
16
|
end
|
17
17
|
|
18
18
|
module Annotate
|
19
|
-
TRUE_RE = /^(true|t|yes|y|1)$/i
|
20
|
-
|
21
|
-
##
|
22
|
-
# The set of available options to customize the behavior of Annotate.
|
23
|
-
#
|
24
|
-
POSITION_OPTIONS = [
|
25
|
-
:position_in_routes, :position_in_class, :position_in_test,
|
26
|
-
:position_in_fixture, :position_in_factory, :position,
|
27
|
-
:position_in_serializer
|
28
|
-
].freeze
|
29
|
-
FLAG_OPTIONS = [
|
30
|
-
:show_indexes, :simple_indexes, :include_version, :exclude_tests,
|
31
|
-
:exclude_fixtures, :exclude_factories, :ignore_model_sub_dir,
|
32
|
-
:format_bare, :format_rdoc, :format_markdown, :sort, :force, :trace,
|
33
|
-
:timestamp, :exclude_serializers, :classified_sort,
|
34
|
-
:show_foreign_keys, :show_complete_foreign_keys,
|
35
|
-
:exclude_scaffolds, :exclude_controllers, :exclude_helpers,
|
36
|
-
:exclude_sti_subclasses, :ignore_unknown_models, :with_comment
|
37
|
-
].freeze
|
38
|
-
OTHER_OPTIONS = [
|
39
|
-
:ignore_columns, :skip_on_db_migrate, :wrapper_open, :wrapper_close,
|
40
|
-
:wrapper, :routes, :hide_limit_column_types, :hide_default_column_types,
|
41
|
-
:ignore_routes, :active_admin
|
42
|
-
].freeze
|
43
|
-
PATH_OPTIONS = [
|
44
|
-
:require, :model_dir, :root_dir
|
45
|
-
].freeze
|
46
|
-
|
47
|
-
def self.all_options
|
48
|
-
[POSITION_OPTIONS, FLAG_OPTIONS, PATH_OPTIONS, OTHER_OPTIONS]
|
49
|
-
end
|
50
|
-
|
51
19
|
##
|
52
20
|
# Set default values that can be overridden via environment variables.
|
53
21
|
#
|
@@ -55,9 +23,9 @@ module Annotate
|
|
55
23
|
return if @has_set_defaults
|
56
24
|
@has_set_defaults = true
|
57
25
|
|
58
|
-
options = HashWithIndifferentAccess.new(options)
|
26
|
+
options = ActiveSupport::HashWithIndifferentAccess.new(options)
|
59
27
|
|
60
|
-
|
28
|
+
Constants::ALL_ANNOTATE_OPTIONS.flatten.each do |key|
|
61
29
|
if options.key?(key)
|
62
30
|
default_value = if options[key].is_a?(Array)
|
63
31
|
options[key].join(',')
|
@@ -75,68 +43,42 @@ module Annotate
|
|
75
43
|
# TODO: what is the difference between this and set_defaults?
|
76
44
|
#
|
77
45
|
def self.setup_options(options = {})
|
78
|
-
POSITION_OPTIONS.each do |key|
|
79
|
-
options[key] = fallback(ENV[key.to_s], ENV['position'], 'before')
|
46
|
+
Constants::POSITION_OPTIONS.each do |key|
|
47
|
+
options[key] = Annotate::Helpers.fallback(ENV[key.to_s], ENV['position'], 'before')
|
80
48
|
end
|
81
|
-
FLAG_OPTIONS.each do |key|
|
82
|
-
options[key] = true?(ENV[key.to_s])
|
49
|
+
Constants::FLAG_OPTIONS.each do |key|
|
50
|
+
options[key] = Annotate::Helpers.true?(ENV[key.to_s])
|
83
51
|
end
|
84
|
-
OTHER_OPTIONS.each do |key|
|
52
|
+
Constants::OTHER_OPTIONS.each do |key|
|
85
53
|
options[key] = !ENV[key.to_s].blank? ? ENV[key.to_s] : nil
|
86
54
|
end
|
87
|
-
PATH_OPTIONS.each do |key|
|
55
|
+
Constants::PATH_OPTIONS.each do |key|
|
88
56
|
options[key] = !ENV[key.to_s].blank? ? ENV[key.to_s].split(',') : []
|
89
57
|
end
|
90
58
|
|
59
|
+
options[:additional_file_patterns] ||= []
|
60
|
+
options[:additional_file_patterns] = options[:additional_file_patterns].split(',') if options[:additional_file_patterns].is_a?(String)
|
91
61
|
options[:model_dir] = ['app/models'] if options[:model_dir].empty?
|
92
62
|
|
93
63
|
options[:wrapper_open] ||= options[:wrapper]
|
94
64
|
options[:wrapper_close] ||= options[:wrapper]
|
95
65
|
|
96
66
|
# These were added in 2.7.0 but so this is to revert to old behavior by default
|
97
|
-
options[:exclude_scaffolds] = Annotate.true?(ENV.fetch('exclude_scaffolds', 'true'))
|
98
|
-
options[:exclude_controllers] = Annotate.true?(ENV.fetch('exclude_controllers', 'true'))
|
99
|
-
options[:exclude_helpers] = Annotate.true?(ENV.fetch('exclude_helpers', 'true'))
|
67
|
+
options[:exclude_scaffolds] = Annotate::Helpers.true?(ENV.fetch('exclude_scaffolds', 'true'))
|
68
|
+
options[:exclude_controllers] = Annotate::Helpers.true?(ENV.fetch('exclude_controllers', 'true'))
|
69
|
+
options[:exclude_helpers] = Annotate::Helpers.true?(ENV.fetch('exclude_helpers', 'true'))
|
100
70
|
|
101
71
|
options
|
102
72
|
end
|
103
73
|
|
104
|
-
def self.reset_options
|
105
|
-
all_options.flatten.each { |key| ENV[key.to_s] = nil }
|
106
|
-
end
|
107
|
-
|
108
|
-
def self.skip_on_migration?
|
109
|
-
ENV['ANNOTATE_SKIP_ON_DB_MIGRATE'] =~ TRUE_RE || ENV['skip_on_db_migrate'] =~ TRUE_RE
|
110
|
-
end
|
111
|
-
|
112
|
-
def self.include_routes?
|
113
|
-
ENV['routes'] =~ TRUE_RE
|
114
|
-
end
|
115
|
-
|
116
|
-
def self.include_models?
|
117
|
-
ENV['routes'] !~ TRUE_RE
|
118
|
-
end
|
119
|
-
|
120
|
-
def self.loaded_tasks=(val)
|
121
|
-
@loaded_tasks = val
|
122
|
-
end
|
123
|
-
|
124
|
-
def self.loaded_tasks
|
125
|
-
@loaded_tasks
|
126
|
-
end
|
127
|
-
|
128
74
|
def self.load_tasks
|
129
|
-
return if
|
130
|
-
self.loaded_tasks = true
|
75
|
+
return if @tasks_loaded
|
131
76
|
|
132
77
|
Dir[File.join(File.dirname(__FILE__), 'tasks', '**/*.rake')].each do |rake|
|
133
78
|
load rake
|
134
79
|
end
|
135
|
-
end
|
136
80
|
|
137
|
-
|
138
|
-
options[:require].count > 0 &&
|
139
|
-
options[:require].each { |path| require path }
|
81
|
+
@tasks_loaded = true
|
140
82
|
end
|
141
83
|
|
142
84
|
def self.eager_load(options)
|
@@ -192,13 +134,12 @@ module Annotate
|
|
192
134
|
Rake::Task[:set_annotation_options].invoke
|
193
135
|
end
|
194
136
|
|
195
|
-
|
196
|
-
|
197
|
-
end
|
137
|
+
class << self
|
138
|
+
private
|
198
139
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
140
|
+
def load_requires(options)
|
141
|
+
options[:require].count > 0 &&
|
142
|
+
options[:require].each { |path| require path }
|
143
|
+
end
|
203
144
|
end
|
204
145
|
end
|
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
require 'bigdecimal'
|
4
4
|
|
5
|
-
|
6
|
-
TRUE_RE = /^(true|t|yes|y|1)$/i
|
5
|
+
require 'annotate/constants'
|
7
6
|
|
7
|
+
module AnnotateModels
|
8
8
|
# Annotate Models plugin use this header
|
9
9
|
COMPAT_PREFIX = '== Schema Info'.freeze
|
10
10
|
COMPAT_PREFIX_MD = '## Schema Info'.freeze
|
@@ -38,9 +38,9 @@ module AnnotateModels
|
|
38
38
|
BLUEPRINTS_TEST_DIR = File.join('test', "blueprints")
|
39
39
|
BLUEPRINTS_SPEC_DIR = File.join('spec', "blueprints")
|
40
40
|
|
41
|
-
# Factory
|
42
|
-
|
43
|
-
|
41
|
+
# Factory Bot https://github.com/thoughtbot/factory_bot
|
42
|
+
FACTORY_BOT_TEST_DIR = File.join('test', "factories")
|
43
|
+
FACTORY_BOT_SPEC_DIR = File.join('spec', "factories")
|
44
44
|
|
45
45
|
# Fabrication https://github.com/paulelliott/fabrication.git
|
46
46
|
FABRICATORS_TEST_DIR = File.join('test', "fabricators")
|
@@ -62,7 +62,7 @@ module AnnotateModels
|
|
62
62
|
|
63
63
|
# Don't show limit (#) on these column types
|
64
64
|
# Example: show "integer" instead of "integer(4)"
|
65
|
-
NO_LIMIT_COL_TYPES = %w(integer boolean).freeze
|
65
|
+
NO_LIMIT_COL_TYPES = %w(integer bigint boolean).freeze
|
66
66
|
|
67
67
|
# Don't show default value for these column types
|
68
68
|
NO_DEFAULT_COL_TYPES = %w(json jsonb hstore).freeze
|
@@ -82,6 +82,8 @@ module AnnotateModels
|
|
82
82
|
}
|
83
83
|
}.freeze
|
84
84
|
|
85
|
+
MAGIC_COMMENT_MATCHER = Regexp.new(/(^#\s*encoding:.*(?:\n|r\n))|(^# coding:.*(?:\n|\r\n))|(^# -\*- coding:.*(?:\n|\r\n))|(^# -\*- encoding\s?:.*(?:\n|\r\n))|(^#\s*frozen_string_literal:.+(?:\n|\r\n))|(^# -\*- frozen_string_literal\s*:.+-\*-(?:\n|\r\n))/).freeze
|
86
|
+
|
85
87
|
class << self
|
86
88
|
def annotate_pattern(options = {})
|
87
89
|
if options[:wrapper_open]
|
@@ -140,12 +142,12 @@ module AnnotateModels
|
|
140
142
|
File.join(root_directory, EXEMPLARS_SPEC_DIR, "%MODEL_NAME%_exemplar.rb"),
|
141
143
|
File.join(root_directory, BLUEPRINTS_TEST_DIR, "%MODEL_NAME%_blueprint.rb"),
|
142
144
|
File.join(root_directory, BLUEPRINTS_SPEC_DIR, "%MODEL_NAME%_blueprint.rb"),
|
143
|
-
File.join(root_directory,
|
144
|
-
File.join(root_directory,
|
145
|
-
File.join(root_directory,
|
146
|
-
File.join(root_directory,
|
147
|
-
File.join(root_directory,
|
148
|
-
File.join(root_directory,
|
145
|
+
File.join(root_directory, FACTORY_BOT_TEST_DIR, "%MODEL_NAME%_factory.rb"), # (old style)
|
146
|
+
File.join(root_directory, FACTORY_BOT_SPEC_DIR, "%MODEL_NAME%_factory.rb"), # (old style)
|
147
|
+
File.join(root_directory, FACTORY_BOT_TEST_DIR, "%TABLE_NAME%.rb"), # (new style)
|
148
|
+
File.join(root_directory, FACTORY_BOT_SPEC_DIR, "%TABLE_NAME%.rb"), # (new style)
|
149
|
+
File.join(root_directory, FACTORY_BOT_TEST_DIR, "%PLURALIZED_MODEL_NAME%.rb"), # (new style)
|
150
|
+
File.join(root_directory, FACTORY_BOT_SPEC_DIR, "%PLURALIZED_MODEL_NAME%.rb"), # (new style)
|
149
151
|
File.join(root_directory, FABRICATORS_TEST_DIR, "%MODEL_NAME%_fabricator.rb"),
|
150
152
|
File.join(root_directory, FABRICATORS_SPEC_DIR, "%MODEL_NAME%_fabricator.rb")
|
151
153
|
]
|
@@ -154,18 +156,20 @@ module AnnotateModels
|
|
154
156
|
def serialize_files(root_directory)
|
155
157
|
[
|
156
158
|
File.join(root_directory, SERIALIZERS_DIR, "%MODEL_NAME%_serializer.rb"),
|
157
|
-
File.join(root_directory, SERIALIZERS_TEST_DIR, "%MODEL_NAME%
|
159
|
+
File.join(root_directory, SERIALIZERS_TEST_DIR, "%MODEL_NAME%_serializer_test.rb"),
|
158
160
|
File.join(root_directory, SERIALIZERS_SPEC_DIR, "%MODEL_NAME%_serializer_spec.rb")
|
159
161
|
]
|
160
162
|
end
|
161
163
|
|
162
|
-
def files_by_pattern(root_directory, pattern_type)
|
164
|
+
def files_by_pattern(root_directory, pattern_type, options)
|
163
165
|
case pattern_type
|
164
166
|
when 'test' then test_files(root_directory)
|
165
167
|
when 'fixture' then fixture_files(root_directory)
|
166
168
|
when 'scaffold' then scaffold_files(root_directory)
|
167
169
|
when 'factory' then factory_files(root_directory)
|
168
170
|
when 'serializer' then serialize_files(root_directory)
|
171
|
+
when 'additional_file_patterns'
|
172
|
+
[options[:additional_file_patterns] || []].flatten
|
169
173
|
when 'controller'
|
170
174
|
[File.join(root_directory, CONTROLLER_DIR, "%PLURALIZED_MODEL_NAME%_controller.rb")]
|
171
175
|
when 'admin'
|
@@ -177,14 +181,20 @@ module AnnotateModels
|
|
177
181
|
end
|
178
182
|
end
|
179
183
|
|
180
|
-
def get_patterns(pattern_types = [])
|
184
|
+
def get_patterns(options, pattern_types = [])
|
181
185
|
current_patterns = []
|
182
186
|
root_dir.each do |root_directory|
|
183
187
|
Array(pattern_types).each do |pattern_type|
|
184
|
-
|
188
|
+
patterns = files_by_pattern(root_directory, pattern_type, options)
|
189
|
+
|
190
|
+
current_patterns += if pattern_type.to_sym == :additional_file_patterns
|
191
|
+
patterns
|
192
|
+
else
|
193
|
+
patterns.map { |p| p.sub(/^[\/]*/, '') }
|
194
|
+
end
|
185
195
|
end
|
186
196
|
end
|
187
|
-
current_patterns
|
197
|
+
current_patterns
|
188
198
|
end
|
189
199
|
|
190
200
|
# Simple quoting for the default column value
|
@@ -236,16 +246,7 @@ module AnnotateModels
|
|
236
246
|
info << "# #{ '-' * ( max_size + md_names_overhead ) } | #{'-' * md_type_allowance} | #{ '-' * 27 }\n"
|
237
247
|
end
|
238
248
|
|
239
|
-
cols =
|
240
|
-
klass.columns.reject do |col|
|
241
|
-
col.name.match(/#{ignore_columns}/)
|
242
|
-
end
|
243
|
-
else
|
244
|
-
klass.columns
|
245
|
-
end
|
246
|
-
|
247
|
-
cols = cols.sort_by(&:name) if options[:sort]
|
248
|
-
cols = classified_sort(cols) if options[:classified_sort]
|
249
|
+
cols = columns(klass, options)
|
249
250
|
cols.each do |col|
|
250
251
|
col_type = get_col_type(col)
|
251
252
|
attrs = []
|
@@ -256,8 +257,8 @@ module AnnotateModels
|
|
256
257
|
|
257
258
|
if col_type == 'decimal'
|
258
259
|
col_type << "(#{col.precision}, #{col.scale})"
|
259
|
-
elsif
|
260
|
-
if col.limit
|
260
|
+
elsif !%w[spatial geometry geography].include?(col_type)
|
261
|
+
if col.limit && !options[:format_yard]
|
261
262
|
if col.limit.is_a? Array
|
262
263
|
attrs << "(#{col.limit.join(', ')})"
|
263
264
|
else
|
@@ -296,12 +297,16 @@ module AnnotateModels
|
|
296
297
|
end
|
297
298
|
if options[:format_rdoc]
|
298
299
|
info << sprintf("# %-#{max_size}.#{max_size}s<tt>%s</tt>", "*#{col_name}*::", attrs.unshift(col_type).join(", ")).rstrip + "\n"
|
300
|
+
elsif options[:format_yard]
|
301
|
+
info << sprintf("# @!attribute #{col_name}") + "\n"
|
302
|
+
ruby_class = col.respond_to?(:array) && col.array ? "Array<#{map_col_type_to_ruby_classes(col_type)}>": map_col_type_to_ruby_classes(col_type)
|
303
|
+
info << sprintf("# @return [#{ruby_class}]") + "\n"
|
299
304
|
elsif options[:format_markdown]
|
300
|
-
name_remainder = max_size - col_name.length
|
305
|
+
name_remainder = max_size - col_name.length - non_ascii_length(col_name)
|
301
306
|
type_remainder = (md_type_allowance - 2) - col_type.length
|
302
307
|
info << (sprintf("# **`%s`**%#{name_remainder}s | `%s`%#{type_remainder}s | `%s`", col_name, " ", col_type, " ", attrs.join(", ").rstrip)).gsub('``', ' ').rstrip + "\n"
|
303
308
|
else
|
304
|
-
info <<
|
309
|
+
info << format_default(col_name, max_size, col_type, bare_type_allowance, attrs)
|
305
310
|
end
|
306
311
|
end
|
307
312
|
|
@@ -362,7 +367,7 @@ module AnnotateModels
|
|
362
367
|
end
|
363
368
|
|
364
369
|
def get_col_type(col)
|
365
|
-
if col.respond_to?(:bigint?) && col.bigint?
|
370
|
+
if (col.respond_to?(:bigint?) && col.bigint?) || /\Abigint\b/ =~ col.sql_type
|
366
371
|
'bigint'
|
367
372
|
else
|
368
373
|
(col.type || col.sql_type).to_s
|
@@ -464,7 +469,10 @@ module AnnotateModels
|
|
464
469
|
foreign_keys = klass.connection.foreign_keys(klass.table_name)
|
465
470
|
return '' if foreign_keys.empty?
|
466
471
|
|
467
|
-
format_name =
|
472
|
+
format_name = lambda do |fk|
|
473
|
+
return fk.options[:column] if fk.name.blank?
|
474
|
+
options[:show_complete_foreign_keys] ? fk.name : fk.name.gsub(/(?<=^fk_rails_)[0-9a-f]{10}$/, '...')
|
475
|
+
end
|
468
476
|
|
469
477
|
max_size = foreign_keys.map(&format_name).map(&:size).max + 1
|
470
478
|
foreign_keys.sort_by {|fk| [format_name.call(fk), fk.column]}.each do |fk|
|
@@ -498,64 +506,63 @@ module AnnotateModels
|
|
498
506
|
# :before, :top, :after or :bottom. Default is :before.
|
499
507
|
#
|
500
508
|
def annotate_one_file(file_name, info_block, position, options = {})
|
501
|
-
|
502
|
-
|
503
|
-
|
509
|
+
return false unless File.exist?(file_name)
|
510
|
+
old_content = File.read(file_name)
|
511
|
+
return false if old_content =~ /#{SKIP_ANNOTATION_PREFIX}.*\n/
|
504
512
|
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
513
|
+
# Ignore the Schema version line because it changes with each migration
|
514
|
+
header_pattern = /(^# Table name:.*?\n(#.*[\r]?\n)*[\r]?)/
|
515
|
+
old_header = old_content.match(header_pattern).to_s
|
516
|
+
new_header = info_block.match(header_pattern).to_s
|
509
517
|
|
510
|
-
|
511
|
-
|
512
|
-
|
518
|
+
column_pattern = /^#[\t ]+[\w\*\.`]+[\t ]+.+$/
|
519
|
+
old_columns = old_header && old_header.scan(column_pattern).sort
|
520
|
+
new_columns = new_header && new_header.scan(column_pattern).sort
|
513
521
|
|
514
|
-
|
522
|
+
return false if old_columns == new_columns && !options[:force]
|
515
523
|
|
516
|
-
|
517
|
-
return false
|
518
|
-
else
|
519
|
-
# Replace inline the old schema info with the new schema info
|
520
|
-
new_content = old_content.sub(annotate_pattern(options), info_block + "\n")
|
524
|
+
abort "annotate error. #{file_name} needs to be updated, but annotate was run with `--frozen`." if options[:frozen]
|
521
525
|
|
522
|
-
|
523
|
-
|
524
|
-
|
526
|
+
# Replace inline the old schema info with the new schema info
|
527
|
+
wrapper_open = options[:wrapper_open] ? "# #{options[:wrapper_open]}\n" : ""
|
528
|
+
wrapper_close = options[:wrapper_close] ? "# #{options[:wrapper_close]}\n" : ""
|
529
|
+
wrapped_info_block = "#{wrapper_open}#{info_block}#{wrapper_close}"
|
525
530
|
|
526
|
-
|
527
|
-
wrapper_close = options[:wrapper_close] ? "# #{options[:wrapper_close]}\n" : ""
|
528
|
-
wrapped_info_block = "#{wrapper_open}#{info_block}#{wrapper_close}"
|
529
|
-
# if there *was* no old schema info (no substitution happened) or :force was passed,
|
530
|
-
# we simply need to insert it in correct position
|
531
|
-
if new_content == old_content || options[:force]
|
532
|
-
old_content.gsub!(magic_comment_matcher, '')
|
533
|
-
old_content.sub!(annotate_pattern(options), '')
|
534
|
-
|
535
|
-
new_content = if %w(after bottom).include?(options[position].to_s)
|
536
|
-
magic_comments_block + (old_content.rstrip + "\n\n" + wrapped_info_block)
|
537
|
-
else
|
538
|
-
magic_comments_block + wrapped_info_block + "\n" + old_content
|
539
|
-
end
|
540
|
-
end
|
531
|
+
old_annotation = old_content.match(annotate_pattern(options)).to_s
|
541
532
|
|
542
|
-
|
543
|
-
|
544
|
-
|
533
|
+
# if there *was* no old schema info or :force was passed, we simply
|
534
|
+
# need to insert it in correct position
|
535
|
+
if old_annotation.empty? || options[:force]
|
536
|
+
magic_comments_block = magic_comments_as_string(old_content)
|
537
|
+
old_content.gsub!(MAGIC_COMMENT_MATCHER, '')
|
538
|
+
old_content.sub!(annotate_pattern(options), '')
|
539
|
+
|
540
|
+
new_content = if %w(after bottom).include?(options[position].to_s)
|
541
|
+
magic_comments_block + (old_content.rstrip + "\n\n" + wrapped_info_block)
|
542
|
+
elsif magic_comments_block.empty?
|
543
|
+
magic_comments_block + wrapped_info_block + old_content.lstrip
|
544
|
+
else
|
545
|
+
magic_comments_block + "\n" + wrapped_info_block + old_content.lstrip
|
546
|
+
end
|
545
547
|
else
|
546
|
-
|
548
|
+
# replace the old annotation with the new one
|
549
|
+
|
550
|
+
# keep the surrounding whitespace the same
|
551
|
+
space_match = old_annotation.match(/\A(?<start>\s*).*?\n(?<end>\s*)\z/m)
|
552
|
+
new_annotation = space_match[:start] + wrapped_info_block + space_match[:end]
|
553
|
+
|
554
|
+
new_content = old_content.sub(annotate_pattern(options), new_annotation)
|
547
555
|
end
|
548
|
-
end
|
549
556
|
|
550
|
-
|
551
|
-
|
557
|
+
File.open(file_name, 'wb') { |f| f.puts new_content }
|
558
|
+
true
|
552
559
|
end
|
553
560
|
|
554
561
|
def magic_comments_as_string(content)
|
555
|
-
magic_comments = content.scan(
|
562
|
+
magic_comments = content.scan(MAGIC_COMMENT_MATCHER).flatten.compact
|
556
563
|
|
557
564
|
if magic_comments.any?
|
558
|
-
magic_comments.join
|
565
|
+
magic_comments.join
|
559
566
|
else
|
560
567
|
''
|
561
568
|
end
|
@@ -578,8 +585,9 @@ module AnnotateModels
|
|
578
585
|
end
|
579
586
|
|
580
587
|
def matched_types(options)
|
581
|
-
types = MATCHED_TYPES
|
582
|
-
types << 'admin' if options[:active_admin] =~ TRUE_RE && !types.include?('admin')
|
588
|
+
types = MATCHED_TYPES.dup
|
589
|
+
types << 'admin' if options[:active_admin] =~ Annotate::Constants::TRUE_RE && !types.include?('admin')
|
590
|
+
types << 'additional_file_patterns' if options[:additional_file_patterns].present?
|
583
591
|
|
584
592
|
types
|
585
593
|
end
|
@@ -631,8 +639,11 @@ module AnnotateModels
|
|
631
639
|
end
|
632
640
|
|
633
641
|
next if options[exclusion_key]
|
634
|
-
|
642
|
+
|
643
|
+
get_patterns(options, key)
|
635
644
|
.map { |f| resolve_filename(f, model_name, table_name) }
|
645
|
+
.map { |f| expand_glob_into_files(f) }
|
646
|
+
.flatten
|
636
647
|
.each do |f|
|
637
648
|
if annotate_one_file(f, info, position_key, options_with_position(options, position_key))
|
638
649
|
annotated << f
|
@@ -790,6 +801,10 @@ module AnnotateModels
|
|
790
801
|
end
|
791
802
|
end
|
792
803
|
|
804
|
+
def expand_glob_into_files(glob)
|
805
|
+
Dir.glob(glob)
|
806
|
+
end
|
807
|
+
|
793
808
|
def annotate_model_file(annotated, file, header, options)
|
794
809
|
begin
|
795
810
|
return false if /#{SKIP_ANNOTATION_PREFIX}.*/ =~ (File.exist?(file) ? File.read(file) : '')
|
@@ -827,7 +842,7 @@ module AnnotateModels
|
|
827
842
|
model_file_name = file
|
828
843
|
deannotated_klass = true if remove_annotation_of_file(model_file_name, options)
|
829
844
|
|
830
|
-
get_patterns(matched_types(options))
|
845
|
+
get_patterns(options, matched_types(options))
|
831
846
|
.map { |f| resolve_filename(f, model_name, table_name) }
|
832
847
|
.each do |f|
|
833
848
|
if File.exist?(f)
|
@@ -883,18 +898,104 @@ module AnnotateModels
|
|
883
898
|
end
|
884
899
|
|
885
900
|
def max_schema_info_width(klass, options)
|
901
|
+
cols = columns(klass, options)
|
902
|
+
|
886
903
|
if with_comments?(klass, options)
|
887
|
-
max_size =
|
888
|
-
column.name.size + (column.comment ? column.comment
|
904
|
+
max_size = cols.map do |column|
|
905
|
+
column.name.size + (column.comment ? width(column.comment) : 0)
|
889
906
|
end.max || 0
|
890
907
|
max_size += 2
|
891
908
|
else
|
892
|
-
max_size =
|
909
|
+
max_size = cols.map(&:name).map(&:size).max
|
893
910
|
end
|
894
911
|
max_size += options[:format_rdoc] ? 5 : 1
|
895
912
|
|
896
913
|
max_size
|
897
914
|
end
|
915
|
+
|
916
|
+
def format_default(col_name, max_size, col_type, bare_type_allowance, attrs)
|
917
|
+
sprintf("# %s:%s %s", mb_chars_ljust(col_name, max_size), mb_chars_ljust(col_type, bare_type_allowance), attrs.join(", ")).rstrip + "\n"
|
918
|
+
end
|
919
|
+
|
920
|
+
def width(string)
|
921
|
+
string.chars.inject(0) { |acc, elem| acc + (elem.bytesize == 3 ? 2 : 1) }
|
922
|
+
end
|
923
|
+
|
924
|
+
def mb_chars_ljust(string, length)
|
925
|
+
string = string.to_s
|
926
|
+
padding = length - width(string)
|
927
|
+
if padding > 0
|
928
|
+
string + (' ' * padding)
|
929
|
+
else
|
930
|
+
string[0..length-1]
|
931
|
+
end
|
932
|
+
end
|
933
|
+
|
934
|
+
def non_ascii_length(string)
|
935
|
+
string.to_s.chars.reject(&:ascii_only?).length
|
936
|
+
end
|
937
|
+
|
938
|
+
def map_col_type_to_ruby_classes(col_type)
|
939
|
+
case col_type
|
940
|
+
when 'integer' then Integer.to_s
|
941
|
+
when 'float' then Float.to_s
|
942
|
+
when 'decimal' then BigDecimal.to_s
|
943
|
+
when 'datetime', 'timestamp', 'time' then Time.to_s
|
944
|
+
when 'date' then Date.to_s
|
945
|
+
when 'text', 'string', 'binary', 'inet', 'uuid' then String.to_s
|
946
|
+
when 'json', 'jsonb' then Hash.to_s
|
947
|
+
when 'boolean' then 'Boolean'
|
948
|
+
end
|
949
|
+
end
|
950
|
+
|
951
|
+
def columns(klass, options)
|
952
|
+
cols = klass.columns
|
953
|
+
cols += translated_columns(klass)
|
954
|
+
|
955
|
+
if ignore_columns = options[:ignore_columns]
|
956
|
+
cols = cols.reject do |col|
|
957
|
+
col.name.match(/#{ignore_columns}/)
|
958
|
+
end
|
959
|
+
end
|
960
|
+
|
961
|
+
cols = cols.sort_by(&:name) if options[:sort]
|
962
|
+
cols = classified_sort(cols) if options[:classified_sort]
|
963
|
+
|
964
|
+
cols
|
965
|
+
end
|
966
|
+
|
967
|
+
##
|
968
|
+
# Add columns managed by the globalize gem if this gem is being used.
|
969
|
+
def translated_columns(klass)
|
970
|
+
return [] unless klass.respond_to? :translation_class
|
971
|
+
|
972
|
+
ignored_cols = ignored_translation_table_colums(klass)
|
973
|
+
klass.translation_class.columns.reject do |col|
|
974
|
+
ignored_cols.include? col.name.to_sym
|
975
|
+
end
|
976
|
+
end
|
977
|
+
|
978
|
+
##
|
979
|
+
# These are the columns that the globalize gem needs to work but
|
980
|
+
# are not necessary for the models to be displayed as annotations.
|
981
|
+
def ignored_translation_table_colums(klass)
|
982
|
+
# Construct the foreign column name in the translations table
|
983
|
+
# eg. Model: Car, foreign column name: car_id
|
984
|
+
foreign_column_name = [
|
985
|
+
klass.translation_class.to_s
|
986
|
+
.gsub('::Translation', '').gsub('::', '_')
|
987
|
+
.downcase,
|
988
|
+
'_id'
|
989
|
+
].join.to_sym
|
990
|
+
|
991
|
+
[
|
992
|
+
:id,
|
993
|
+
:created_at,
|
994
|
+
:updated_at,
|
995
|
+
:locale,
|
996
|
+
foreign_column_name
|
997
|
+
]
|
998
|
+
end
|
898
999
|
end
|
899
1000
|
|
900
1001
|
class BadModelFileError < LoadError
|