annotate 2.7.4 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|