annotate 2.7.2 → 2.7.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 365398d9352685a831b09fe60f460131a0fbe2ab
4
- data.tar.gz: ba3fce8c6c97125af42c90310adac5272c6ef78c
2
+ SHA256:
3
+ metadata.gz: d4cd9e83172aed3fe8975a79f1cfeb076a1af786e547472c4d22130e07c559c9
4
+ data.tar.gz: a276b3e5964dd0b6fd5c033be8d1d143989abbbe3c43e0a56e399455c644cbe2
5
5
  SHA512:
6
- metadata.gz: 2f219cec844284bd213ca826a12553b862a9ec6dcfb2ccc271a2821576989dba630e5ad86951ab2fba2d6df02b77c5468b6f1a9b60ff14cf71b23c90779e9a78
7
- data.tar.gz: 9fdc058f6de547f0e3fa13f0b7ddaa931b4be5ad9ba4e041fb95002b6d9bd46631d0f3df5c1428e71774b089f7899d1e3332b5af00f0db8d1fe56dc249044bc3
6
+ metadata.gz: f44658cc7947064044bbc96fc2d9a4b2e077cc4c6b2934b71477081a0a9e42ad0d01bc8ac0e43d6707f8576cdcbf49af85562441dc7dde5f7517219ffb34902c
7
+ data.tar.gz: 5d16e2603ef7c5a3428d157f9d85a51a02d6386eeafd984d16a133dd55c1dec61dc14e15be6cf3ae349d34403b358785cba37f0b262b3cccdb9f750fd8043eb6
@@ -1,3 +1,6 @@
1
+ == 2.7.3
2
+ See https://github.com/ctran/annotate_models/releases/tag/v2.7.3
3
+
1
4
  == 2.7.2
2
5
  See https://github.com/ctran/annotate_models/releases/tag/v2.7.2
3
6
 
@@ -17,7 +17,7 @@ your...
17
17
  - Object Daddy exemplars
18
18
  - Machinist blueprints
19
19
  - Fabrication fabricators
20
- - Thoughtbot's factory_girl factories, i.e. the (spec|test)/factories/<model>_factory.rb files
20
+ - Thoughtbot's factory_bot factories, i.e. the (spec|test)/factories/<model>_factory.rb files
21
21
  - routes.rb file (for Rails projects)
22
22
 
23
23
  The schema comment looks like this:
@@ -55,11 +55,15 @@ Also, if you pass the -r option, it'll annotate routes.rb with the output of
55
55
 
56
56
  Into Gemfile from rubygems.org:
57
57
 
58
- gem 'annotate'
58
+ group :development do
59
+ gem 'annotate'
60
+ end
59
61
 
60
62
  Into Gemfile from Github:
61
63
 
62
- gem 'annotate', git: 'https://github.com/ctran/annotate_models.git'
64
+ group :development do
65
+ gem 'annotate', git: 'https://github.com/ctran/annotate_models.git'
66
+ end
63
67
 
64
68
  Into environment gems from rubygems.org:
65
69
 
@@ -154,14 +158,15 @@ If you want to run <code>rake db:migrate</code> as a one-off without running ann
154
158
  you can do so with a simple environment variable, instead of editing the
155
159
  +.rake+ file:
156
160
 
157
- skip_on_db_migrate=1 rake db:migrate
161
+ ANNOTATE_SKIP_ON_DB_MIGRATE=1 rake db:migrate
158
162
 
159
163
 
160
164
  == Options
161
165
 
162
166
  Usage: annotate [options] [model_file]*
163
167
  -d, --delete Remove annotations from all model files or the routes.rb file
164
- -p, --position [before|top|after|bottom] Place the annotations at the top (before) or the bottom (after) of the model/test/fixture/factory/routes file(s)
168
+ -p [before|top|after|bottom], Place the annotations at the top (before) or the bottom (after) of the model/test/fixture/factory/route/serializer file(s)
169
+ --position
165
170
  --pc, --position-in-class [before|top|after|bottom]
166
171
  Place the annotations at the top (before) or the bottom (after) of the model file
167
172
  --pf, --position-in-factory [before|top|after|bottom]
@@ -179,15 +184,19 @@ you can do so with a simple environment variable, instead of editing the
179
184
  --wo, --wrapper-open STR Annotation wrapper opening.
180
185
  --wc, --wrapper-close STR Annotation wrapper closing
181
186
  -r, --routes Annotate routes.rb with the output of 'rake routes'
182
- -aa, --active-admin Annotate all activeadmin models
187
+ -a, --active-admin Annotate active_admin models
183
188
  -v, --version Show the current version of this gem
184
189
  -m, --show-migration Include the migration version number in the annotation
185
- -i, --show-indexes List the table's database indexes in the annotation
186
190
  -k, --show-foreign-keys List the table's foreign key constraints in the annotation
191
+ --ck, --complete-foreign-keys
192
+ Complete foreign key names in the annotation
193
+ -i, --show-indexes List the table's database indexes in the annotation
187
194
  -s, --simple-indexes Concat the column's related indexes in the annotation
188
195
  --model-dir dir Annotate model files stored in dir rather than app/models, separate multiple dirs with commas
196
+ --root-dir dir Annotate files stored within root dir projects, separate multiple dirs with commas
189
197
  --ignore-model-subdirects Ignore subdirectories of the models directory
190
198
  --sort Sort columns alphabetically, rather than in creation order
199
+ --classified-sort Sort columns alphabetically, but first goes id, then the rest columns, then the timestamp columns and then the association columns
191
200
  -R, --require path Additional file to require before loading models, may be used multiple times
192
201
  -e [tests,fixtures,factories,serializers],
193
202
  --exclude Do not annotate fixtures, test files, factories, and/or serializers
@@ -197,6 +206,13 @@ you can do so with a simple environment variable, instead of editing the
197
206
  --timestamp Include timestamp in (routes) annotation
198
207
  --trace If unable to annotate a file, print the full stack trace, not just the exception message.
199
208
  -I, --ignore-columns REGEX don't annotate columns that match a given REGEX (i.e., `annotate -I '^(id|updated_at|created_at)'`
209
+ --ignore-routes REGEX don't annotate routes that match a given REGEX (i.e., `annotate -I '(mobile|resque|pghero)'`
210
+ --hide-limit-column-types VALUES
211
+ don't show limit for given column types, separated by commas (i.e., `integer,boolean,text`)
212
+ --hide-default-column-types VALUES
213
+ don't show default for given column types, separated by commas (i.e., `json,jsonb,hstore`)
214
+ --ignore-unknown-models don't display warnings for bad model files
215
+ --with-comment include database comments in model annotations
200
216
 
201
217
 
202
218
 
@@ -242,7 +258,7 @@ extra carefully, and consider using one.
242
258
 
243
259
  == Links
244
260
 
245
- - Factory Girl: http://github.com/thoughtbot/factory_girl
261
+ - Factory Bot: http://github.com/thoughtbot/factory_bot
246
262
  - Object Daddy: http://github.com/flogic/object_daddy
247
263
  - Machinist: http://github.com/notahat/machinist
248
264
  - Fabrication: http://github.com/paulelliott/fabrication
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
7
7
  s.name = 'annotate'
8
8
  s.version = Annotate.version
9
9
 
10
- s.required_ruby_version = '>= 1.9.3'
10
+ s.required_ruby_version = '>= 2.2.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.'
@@ -90,7 +90,7 @@ OptionParser.new do |opts|
90
90
  ENV['routes'] = 'true'
91
91
  end
92
92
 
93
- opts.on('-aa', '--active-admin', 'Annotate active_admin models') do
93
+ opts.on('-a', '--active-admin', 'Annotate active_admin models') do
94
94
  ENV['active_admin'] = 'true'
95
95
  end
96
96
 
@@ -107,6 +107,12 @@ OptionParser.new do |opts|
107
107
  ENV['show_foreign_keys'] = 'yes'
108
108
  end
109
109
 
110
+ opts.on('--ck',
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
+
110
116
  opts.on('-i', '--show-indexes',
111
117
  "List the table's database indexes in the annotation") do
112
118
  ENV['show_indexes'] = 'yes'
@@ -191,12 +197,16 @@ OptionParser.new do |opts|
191
197
  opts.on('--ignore-unknown-models', "don't display warnings for bad model files") do |values|
192
198
  ENV['ignore_unknown_models'] = 'true'
193
199
  end
200
+
201
+ opts.on('--with-comment', "include database comments in model annotations") do |values|
202
+ ENV['with_comment'] = 'true'
203
+ end
194
204
  end.parse!
195
205
 
196
206
  options = Annotate.setup_options(
197
207
  is_rake: ENV['is_rake'] && !ENV['is_rake'].empty?
198
208
  )
199
- Annotate.eager_load(options)
209
+ Annotate.eager_load(options) if Annotate.include_models?
200
210
 
201
211
  AnnotateModels.send(target_action, options) if Annotate.include_models?
202
212
  AnnotateRoutes.send(target_action, options) if Annotate.include_routes?
@@ -30,9 +30,10 @@ module Annotate
30
30
  :show_indexes, :simple_indexes, :include_version, :exclude_tests,
31
31
  :exclude_fixtures, :exclude_factories, :ignore_model_sub_dir,
32
32
  :format_bare, :format_rdoc, :format_markdown, :sort, :force, :trace,
33
- :timestamp, :exclude_serializers, :classified_sort, :show_foreign_keys,
33
+ :timestamp, :exclude_serializers, :classified_sort,
34
+ :show_foreign_keys, :show_complete_foreign_keys,
34
35
  :exclude_scaffolds, :exclude_controllers, :exclude_helpers,
35
- :exclude_sti_subclasses, :ignore_unknown_models
36
+ :exclude_sti_subclasses, :ignore_unknown_models, :with_comment
36
37
  ].freeze
37
38
  OTHER_OPTIONS = [
38
39
  :ignore_columns, :skip_on_db_migrate, :wrapper_open, :wrapper_close,
@@ -105,7 +106,7 @@ module Annotate
105
106
  end
106
107
 
107
108
  def self.skip_on_migration?
108
- ENV['skip_on_db_migrate'] =~ TRUE_RE
109
+ ENV['ANNOTATE_SKIP_ON_DB_MIGRATE'] =~ TRUE_RE || ENV['skip_on_db_migrate'] =~ TRUE_RE
109
110
  end
110
111
 
111
112
  def self.include_routes?
@@ -113,7 +114,7 @@ module Annotate
113
114
  end
114
115
 
115
116
  def self.include_models?
116
- true
117
+ ENV['routes'] !~ TRUE_RE
117
118
  end
118
119
 
119
120
  def self.loaded_tasks=(val)
@@ -168,7 +169,7 @@ module Annotate
168
169
  require 'rake/dsl_definition'
169
170
  rescue StandardError => e
170
171
  # We might just be on an old version of Rake...
171
- puts e.message
172
+ $stderr.puts e.message
172
173
  exit e.status_code
173
174
  end
174
175
  require 'rake'
@@ -12,6 +12,8 @@ module AnnotateModels
12
12
  PREFIX_MD = '## Schema Information'.freeze
13
13
  END_MARK = '== Schema Information End'.freeze
14
14
 
15
+ SKIP_ANNOTATION_PREFIX = '# -\*- SkipSchemaAnnotations'.freeze
16
+
15
17
  MATCHED_TYPES = %w(test fixture factory serializer scaffold controller helper).freeze
16
18
 
17
19
  # File.join for windows reverse bar compat?
@@ -65,12 +67,27 @@ module AnnotateModels
65
67
  # Don't show default value for these column types
66
68
  NO_DEFAULT_COL_TYPES = %w(json jsonb hstore).freeze
67
69
 
70
+ INDEX_CLAUSES = {
71
+ unique: {
72
+ default: 'UNIQUE',
73
+ markdown: '_unique_'
74
+ },
75
+ where: {
76
+ default: 'WHERE',
77
+ markdown: '_where_'
78
+ },
79
+ using: {
80
+ default: 'USING',
81
+ markdown: '_using_'
82
+ }
83
+ }.freeze
84
+
68
85
  class << self
69
86
  def annotate_pattern(options = {})
70
87
  if options[:wrapper_open]
71
- return /(?:^\n?# (?:#{options[:wrapper_open]}).*\n?# (?:#{COMPAT_PREFIX}|#{COMPAT_PREFIX_MD}).*?\n(#.*\n)*\n*)|^\n?# (?:#{COMPAT_PREFIX}|#{COMPAT_PREFIX_MD}).*?\n(#.*\n)*\n*/
88
+ return /(?:^(\n|\r\n)?# (?:#{options[:wrapper_open]}).*(\n|\r\n)?# (?:#{COMPAT_PREFIX}|#{COMPAT_PREFIX_MD}).*?(\n|\r\n)(#.*(\n|\r\n))*(\n|\r\n)*)|^(\n|\r\n)?# (?:#{COMPAT_PREFIX}|#{COMPAT_PREFIX_MD}).*?(\n|\r\n)(#.*(\n|\r\n))*(\n|\r\n)*/
72
89
  end
73
- /^\n?# (?:#{COMPAT_PREFIX}|#{COMPAT_PREFIX_MD}).*?\n(#.*\n)*\n*/
90
+ /^(\n|\r\n)?# (?:#{COMPAT_PREFIX}|#{COMPAT_PREFIX_MD}).*?(\n|\r\n)(#.*(\n|\r\n))*(\n|\r\n)*/
74
91
  end
75
92
 
76
93
  def model_dir
@@ -127,6 +144,8 @@ module AnnotateModels
127
144
  File.join(root_directory, FACTORY_GIRL_SPEC_DIR, "%MODEL_NAME%_factory.rb"), # (old style)
128
145
  File.join(root_directory, FACTORY_GIRL_TEST_DIR, "%TABLE_NAME%.rb"), # (new style)
129
146
  File.join(root_directory, FACTORY_GIRL_SPEC_DIR, "%TABLE_NAME%.rb"), # (new style)
147
+ File.join(root_directory, FACTORY_GIRL_TEST_DIR, "%PLURALIZED_MODEL_NAME%.rb"), # (new style)
148
+ File.join(root_directory, FACTORY_GIRL_SPEC_DIR, "%PLURALIZED_MODEL_NAME%.rb"), # (new style)
130
149
  File.join(root_directory, FABRICATORS_TEST_DIR, "%MODEL_NAME%_fabricator.rb"),
131
150
  File.join(root_directory, FABRICATORS_SPEC_DIR, "%MODEL_NAME%_fabricator.rb")
132
151
  ]
@@ -195,8 +214,8 @@ module AnnotateModels
195
214
  return indexes if indexes.any? || !klass.table_name_prefix
196
215
 
197
216
  # Try to search the table without prefix
198
- table_name.to_s.slice!(klass.table_name_prefix)
199
- klass.connection.indexes(table_name)
217
+ table_name_without_prefix = table_name.to_s.sub(klass.table_name_prefix, '')
218
+ klass.connection.indexes(table_name_without_prefix)
200
219
  end
201
220
 
202
221
  # Use the column information in an ActiveRecord class
@@ -207,11 +226,7 @@ module AnnotateModels
207
226
  info = "# #{header}\n"
208
227
  info << get_schema_header_text(klass, options)
209
228
 
210
- max_size = klass.column_names.map(&:size).max || 0
211
- with_comment = options[:with_comment] && klass.columns.first.respond_to?(:comment)
212
- max_size = klass.columns.map{|col| col.name.size + col.comment.size }.max || 0 if with_comment
213
- max_size += 2 if with_comment
214
- max_size += options[:format_rdoc] ? 5 : 1
229
+ max_size = max_schema_info_width(klass, options)
215
230
  md_names_overhead = 6
216
231
  md_type_allowance = 18
217
232
  bare_type_allowance = 16
@@ -232,7 +247,7 @@ module AnnotateModels
232
247
  cols = cols.sort_by(&:name) if options[:sort]
233
248
  cols = classified_sort(cols) if options[:classified_sort]
234
249
  cols.each do |col|
235
- col_type = (col.type || col.sql_type).to_s
250
+ col_type = get_col_type(col)
236
251
  attrs = []
237
252
  attrs << "default(#{schema_default(klass, col)})" unless col.default.nil? || hide_default?(col_type, options)
238
253
  attrs << 'unsigned' if col.respond_to?(:unsigned?) && col.unsigned?
@@ -274,7 +289,7 @@ module AnnotateModels
274
289
  end
275
290
  end
276
291
  end
277
- col_name = if with_comment
292
+ col_name = if with_comments?(klass, options) && col.comment
278
293
  "#{col.name}(#{col.comment})"
279
294
  else
280
295
  col.name
@@ -337,15 +352,83 @@ module AnnotateModels
337
352
  max_size = indexes.collect{|index| index.name.size}.max + 1
338
353
  indexes.sort_by(&:name).each do |index|
339
354
  index_info << if options[:format_markdown]
340
- sprintf("# * `%s`%s:\n# * **`%s`**\n", index.name, index.unique ? " (_unique_)" : "", Array(index.columns).join("`**\n# * **`"))
355
+ final_index_string_in_markdown(index)
341
356
  else
342
- sprintf("# %-#{max_size}.#{max_size}s %s %s", index.name, "(#{Array(index.columns).join(",")})", index.unique ? "UNIQUE" : "").rstrip + "\n"
357
+ final_index_string(index, max_size)
343
358
  end
344
359
  end
345
360
 
346
361
  index_info
347
362
  end
348
363
 
364
+ def get_col_type(col)
365
+ if col.respond_to?(:bigint?) && col.bigint?
366
+ 'bigint'
367
+ else
368
+ (col.type || col.sql_type).to_s
369
+ end
370
+ end
371
+
372
+ def index_columns_info(index)
373
+ Array(index.columns).map do |col|
374
+ if index.try(:orders) && index.orders[col.to_s]
375
+ "#{col} #{index.orders[col.to_s].upcase}"
376
+ else
377
+ col.to_s.gsub("\r", '\r').gsub("\n", '\n')
378
+ end
379
+ end
380
+ end
381
+
382
+ def index_unique_info(index, format = :default)
383
+ index.unique ? " #{INDEX_CLAUSES[:unique][format]}" : ''
384
+ end
385
+
386
+ def index_where_info(index, format = :default)
387
+ value = index.try(:where).try(:to_s)
388
+ if value.blank?
389
+ ''
390
+ else
391
+ " #{INDEX_CLAUSES[:where][format]} #{value}"
392
+ end
393
+ end
394
+
395
+ def index_using_info(index, format = :default)
396
+ value = index.try(:using) && index.using.try(:to_sym)
397
+ if !value.blank? && value != :btree
398
+ " #{INDEX_CLAUSES[:using][format]} #{value}"
399
+ else
400
+ ''
401
+ end
402
+ end
403
+
404
+ def final_index_string_in_markdown(index)
405
+ details = sprintf(
406
+ "%s%s%s",
407
+ index_unique_info(index, :markdown),
408
+ index_where_info(index, :markdown),
409
+ index_using_info(index, :markdown)
410
+ ).strip
411
+ details = " (#{details})" unless details.blank?
412
+
413
+ sprintf(
414
+ "# * `%s`%s:\n# * **`%s`**\n",
415
+ index.name,
416
+ details,
417
+ index_columns_info(index).join("`**\n# * **`")
418
+ )
419
+ end
420
+
421
+ def final_index_string(index, max_size)
422
+ sprintf(
423
+ "# %-#{max_size}.#{max_size}s %s%s%s%s",
424
+ index.name,
425
+ "(#{index_columns_info(index).join(',')})",
426
+ index_unique_info(index),
427
+ index_where_info(index),
428
+ index_using_info(index)
429
+ ).rstrip + "\n"
430
+ end
431
+
349
432
  def hide_limit?(col_type, options)
350
433
  excludes =
351
434
  if options[:hide_limit_column_types].blank?
@@ -417,7 +500,7 @@ module AnnotateModels
417
500
  def annotate_one_file(file_name, info_block, position, options = {})
418
501
  if File.exist?(file_name)
419
502
  old_content = File.read(file_name)
420
- return false if old_content =~ /# -\*- SkipSchemaAnnotations.*\n/
503
+ return false if old_content =~ /#{SKIP_ANNOTATION_PREFIX}.*\n/
421
504
 
422
505
  # Ignore the Schema version line because it changes with each migration
423
506
  header_pattern = /(^# Table name:.*?\n(#.*[\r]?\n)*[\r]?)/
@@ -428,8 +511,7 @@ module AnnotateModels
428
511
  old_columns = old_header && old_header.scan(column_pattern).sort
429
512
  new_columns = new_header && new_header.scan(column_pattern).sort
430
513
 
431
- magic_comment_matcher = Regexp.new(/(^#\s*encoding:.*\n)|(^# coding:.*\n)|(^# -\*- coding:.*\n)|(^# -\*- encoding\s?:.*\n)|(^#\s*frozen_string_literal:.+\n)|(^# -\*- frozen_string_literal\s*:.+-\*-\n)/)
432
- magic_comments = old_content.scan(magic_comment_matcher).flatten.compact
514
+ magic_comments_block = magic_comments_as_string(old_content)
433
515
 
434
516
  if old_columns == new_columns && !options[:force]
435
517
  return false
@@ -447,13 +529,13 @@ module AnnotateModels
447
529
  # if there *was* no old schema info (no substitution happened) or :force was passed,
448
530
  # we simply need to insert it in correct position
449
531
  if new_content == old_content || options[:force]
450
- old_content.sub!(magic_comment_matcher, '')
532
+ old_content.gsub!(magic_comment_matcher, '')
451
533
  old_content.sub!(annotate_pattern(options), '')
452
534
 
453
535
  new_content = if %w(after bottom).include?(options[position].to_s)
454
- magic_comments.join + (old_content.rstrip + "\n\n" + wrapped_info_block)
536
+ magic_comments_block + (old_content.rstrip + "\n\n" + wrapped_info_block)
455
537
  else
456
- magic_comments.join + wrapped_info_block + "\n" + old_content
538
+ magic_comments_block + wrapped_info_block + "\n" + old_content
457
539
  end
458
540
  end
459
541
 
@@ -465,9 +547,25 @@ module AnnotateModels
465
547
  end
466
548
  end
467
549
 
550
+ def magic_comment_matcher
551
+ 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))/)
552
+ end
553
+
554
+ def magic_comments_as_string(content)
555
+ magic_comments = content.scan(magic_comment_matcher).flatten.compact
556
+
557
+ if magic_comments.any?
558
+ magic_comments.join + "\n"
559
+ else
560
+ ''
561
+ end
562
+ end
563
+
468
564
  def remove_annotation_of_file(file_name, options = {})
469
565
  if File.exist?(file_name)
470
566
  content = File.read(file_name)
567
+ return false if content =~ /#{SKIP_ANNOTATION_PREFIX}.*\n/
568
+
471
569
  wrapper_open = options[:wrapper_open] ? "# #{options[:wrapper_open]}\n" : ''
472
570
  content.sub!(/(#{wrapper_open})?#{annotate_pattern(options)}/, '')
473
571
 
@@ -542,8 +640,8 @@ module AnnotateModels
542
640
  end
543
641
  end
544
642
  rescue StandardError => e
545
- puts "Unable to annotate #{file}: #{e.message}"
546
- puts "\t" + e.backtrace.join("\n\t") if options[:trace]
643
+ $stderr.puts "Unable to annotate #{file}: #{e.message}"
644
+ $stderr.puts "\t" + e.backtrace.join("\n\t") if options[:trace]
547
645
  end
548
646
 
549
647
  annotated
@@ -559,35 +657,53 @@ module AnnotateModels
559
657
  # of model files from root dir. Otherwise we take all the model files
560
658
  # in the model_dir directory.
561
659
  def get_model_files(options)
562
- models = []
563
- unless options[:is_rake]
564
- models = ARGV.dup.reject { |m| m.match(/^(.*)=/) }
565
- end
660
+ model_files = []
566
661
 
567
- if models.empty?
568
- begin
569
- model_dir.each do |dir|
570
- Dir.chdir(dir) do
571
- lst =
572
- if options[:ignore_model_sub_dir]
573
- Dir["*.rb"].map{ |f| [dir, f] }
574
- else
575
- Dir["**/*.rb"].reject{ |f| f["concerns/"] }.map{ |f| [dir, f] }
576
- end
577
- models.concat(lst)
578
- end
579
- end
580
- rescue SystemCallError
581
- puts "No models found in directory '#{model_dir.join("', '")}'."
582
- puts "Either specify models on the command line, or use the --model-dir option."
583
- puts "Call 'annotate --help' for more info."
584
- exit 1
662
+ model_files = list_model_files_from_argument unless options[:is_rake]
663
+
664
+ return model_files unless model_files.empty?
665
+
666
+ model_dir.each do |dir|
667
+ Dir.chdir(dir) do
668
+ list = if options[:ignore_model_sub_dir]
669
+ Dir["*.rb"].map { |f| [dir, f] }
670
+ else
671
+ Dir["**/*.rb"].reject { |f| f["concerns/"] }.map { |f| [dir, f] }
672
+ end
673
+ model_files.concat(list)
585
674
  end
586
675
  end
587
676
 
588
- models
677
+ model_files
678
+ rescue SystemCallError
679
+ $stderr.puts "No models found in directory '#{model_dir.join("', '")}'."
680
+ $stderr.puts "Either specify models on the command line, or use the --model-dir option."
681
+ $stderr.puts "Call 'annotate --help' for more info."
682
+ exit 1
589
683
  end
590
684
 
685
+ def list_model_files_from_argument
686
+ return [] if ARGV.empty?
687
+
688
+ specified_files = ARGV.map { |file| File.expand_path(file) }
689
+
690
+ model_files = model_dir.flat_map do |dir|
691
+ absolute_dir_path = File.expand_path(dir)
692
+ specified_files
693
+ .find_all { |file| file.start_with?(absolute_dir_path) }
694
+ .map { |file| [dir, file.sub("#{absolute_dir_path}/", '')] }
695
+ end
696
+
697
+ if model_files.size != specified_files.size
698
+ puts "The specified file could not be found in directory '#{model_dir.join("', '")}'."
699
+ puts "Call 'annotate --help' for more info."
700
+ exit 1
701
+ end
702
+
703
+ model_files
704
+ end
705
+ private :list_model_files_from_argument
706
+
591
707
  # Retrieve the classes belonging to the model names we're asked to process
592
708
  # Check for namespaced models in subdirectories as well as models
593
709
  # in subdirectories without namespacing.
@@ -595,11 +711,11 @@ module AnnotateModels
595
711
  model_path = file.gsub(/\.rb$/, '')
596
712
  model_dir.each { |dir| model_path = model_path.gsub(/^#{dir}/, '').gsub(/^\//, '') }
597
713
  begin
598
- get_loaded_model(model_path) || raise(BadModelFileError.new)
714
+ get_loaded_model(model_path, file) || raise(BadModelFileError.new)
599
715
  rescue LoadError
600
716
  # this is for non-rails projects, which don't get Rails auto-require magic
601
717
  file_path = File.expand_path(file)
602
- if File.file?(file_path) && silence_warnings { Kernel.require(file_path) }
718
+ if File.file?(file_path) && Kernel.require(file_path)
603
719
  retry
604
720
  elsif model_path =~ /\//
605
721
  model_path = model_path.split('/')[1..-1].join('/').to_s
@@ -610,10 +726,26 @@ module AnnotateModels
610
726
  end
611
727
  end
612
728
 
729
+ # Retrieve loaded model class
730
+ def get_loaded_model(model_path, file)
731
+ loaded_model_class = get_loaded_model_by_path(model_path)
732
+ return loaded_model_class if loaded_model_class
733
+
734
+ # We cannot get loaded model when `model_path` is loaded by Rails
735
+ # auto_load/eager_load paths. Try all possible model paths one by one.
736
+ absolute_file = File.expand_path(file)
737
+ model_paths =
738
+ $LOAD_PATH.select { |path| absolute_file.include?(path) }
739
+ .map { |path| absolute_file.sub(path, '').sub(/\.rb$/, '').sub(/^\//, '') }
740
+ model_paths
741
+ .map { |path| get_loaded_model_by_path(path) }
742
+ .find { |loaded_model| !loaded_model.nil? }
743
+ end
744
+
613
745
  # Retrieve loaded model class by path to the file where it's supposed to be defined.
614
- def get_loaded_model(model_path)
746
+ def get_loaded_model_by_path(model_path)
615
747
  ActiveSupport::Inflector.constantize(ActiveSupport::Inflector.camelize(model_path))
616
- rescue
748
+ rescue StandardError, LoadError
617
749
  # Revert to the old way but it is not really robust
618
750
  ObjectSpace.each_object(::Class)
619
751
  .select do |c|
@@ -624,10 +756,15 @@ module AnnotateModels
624
756
  end
625
757
 
626
758
  def parse_options(options = {})
627
- self.model_dir = options[:model_dir] if options[:model_dir]
759
+ self.model_dir = split_model_dir(options[:model_dir]) if options[:model_dir]
628
760
  self.root_dir = options[:root_dir] if options[:root_dir]
629
761
  end
630
762
 
763
+ def split_model_dir(option_value)
764
+ option_value = option_value.is_a?(Array) ? option_value : option_value.split(',')
765
+ option_value.map(&:strip).reject(&:empty?)
766
+ end
767
+
631
768
  # We're passed a name of things that might be
632
769
  # ActiveRecord models. If we can find the class, and
633
770
  # if its a subclass of ActiveRecord::Base,
@@ -655,7 +792,7 @@ module AnnotateModels
655
792
 
656
793
  def annotate_model_file(annotated, file, header, options)
657
794
  begin
658
- return false if /# -\*- SkipSchemaAnnotations.*/ =~ (File.exist?(file) ? File.read(file) : '')
795
+ return false if /#{SKIP_ANNOTATION_PREFIX}.*/ =~ (File.exist?(file) ? File.read(file) : '')
659
796
  klass = get_model_class(file)
660
797
  do_annotate = klass &&
661
798
  klass < ActiveRecord::Base &&
@@ -666,12 +803,12 @@ module AnnotateModels
666
803
  annotated.concat(annotate(klass, file, header, options)) if do_annotate
667
804
  rescue BadModelFileError => e
668
805
  unless options[:ignore_unknown_models]
669
- puts "Unable to annotate #{file}: #{e.message}"
670
- puts "\t" + e.backtrace.join("\n\t") if options[:trace]
806
+ $stderr.puts "Unable to annotate #{file}: #{e.message}"
807
+ $stderr.puts "\t" + e.backtrace.join("\n\t") if options[:trace]
671
808
  end
672
809
  rescue StandardError => e
673
- puts "Unable to annotate #{file}: #{e.message}"
674
- puts "\t" + e.backtrace.join("\n\t") if options[:trace]
810
+ $stderr.puts "Unable to annotate #{file}: #{e.message}"
811
+ $stderr.puts "\t" + e.backtrace.join("\n\t") if options[:trace]
675
812
  end
676
813
  end
677
814
 
@@ -701,8 +838,8 @@ module AnnotateModels
701
838
  end
702
839
  deannotated << klass if deannotated_klass
703
840
  rescue StandardError => e
704
- puts "Unable to deannotate #{File.join(file)}: #{e.message}"
705
- puts "\t" + e.backtrace.join("\n\t") if options[:trace]
841
+ $stderr.puts "Unable to deannotate #{File.join(file)}: #{e.message}"
842
+ $stderr.puts "\t" + e.backtrace.join("\n\t") if options[:trace]
706
843
  end
707
844
  end
708
845
  puts "Removed annotations from: #{deannotated.join(', ')}"
@@ -737,13 +874,26 @@ module AnnotateModels
737
874
  ([id] << rest_cols << timestamps << associations).flatten.compact
738
875
  end
739
876
 
740
- # Ignore warnings for the duration of the block ()
741
- def silence_warnings
742
- old_verbose = $VERBOSE
743
- $VERBOSE = nil
744
- yield
745
- ensure
746
- $VERBOSE = old_verbose
877
+ private
878
+
879
+ def with_comments?(klass, options)
880
+ options[:with_comment] &&
881
+ klass.columns.first.respond_to?(:comment) &&
882
+ klass.columns.any? { |col| !col.comment.nil? }
883
+ end
884
+
885
+ def max_schema_info_width(klass, options)
886
+ if with_comments?(klass, options)
887
+ max_size = klass.columns.map do |column|
888
+ column.name.size + (column.comment ? column.comment.size : 0)
889
+ end.max || 0
890
+ max_size += 2
891
+ else
892
+ max_size = klass.column_names.map(&:size).max
893
+ end
894
+ max_size += options[:format_rdoc] ? 5 : 1
895
+
896
+ max_size
747
897
  end
748
898
  end
749
899
 
@@ -1,3 +1,5 @@
1
+ # rubocop:disable Metrics/ModuleLength
2
+
1
3
  # == Annotate Routes
2
4
  #
3
5
  # Based on:
@@ -36,7 +38,18 @@ module AnnotateRoutes
36
38
  def header(options = {})
37
39
  routes_map = app_routes_map(options)
38
40
 
39
- out = ["# #{options[:format_markdown] ? PREFIX_MD : PREFIX}" + (options[:timestamp] ? " (Updated #{Time.now.strftime('%Y-%m-%d %H:%M')})" : '')]
41
+ magic_comments_map, routes_map = extract_magic_comments_from_array(routes_map)
42
+
43
+ out = []
44
+
45
+ magic_comments_map.each do |magic_comment|
46
+ out << magic_comment
47
+ end
48
+ out << '' if magic_comments_map.any?
49
+
50
+ out += ["# #{options[:wrapper_open]}"] if options[:wrapper_open]
51
+
52
+ out += ["# #{options[:format_markdown] ? PREFIX_MD : PREFIX}" + (options[:timestamp] ? " (Updated #{Time.now.strftime('%Y-%m-%d %H:%M')})" : '')]
40
53
  out += ['#']
41
54
  return out if routes_map.size.zero?
42
55
 
@@ -51,35 +64,56 @@ module AnnotateRoutes
51
64
  out += ["# #{content(routes_map[0], maxs, options)}"]
52
65
  end
53
66
 
54
- out + routes_map[1..-1].map { |line| "# #{content(options[:format_markdown] ? line.split(' ') : line, maxs, options)}" }
67
+ out += routes_map[1..-1].map { |line| "# #{content(options[:format_markdown] ? line.split(' ') : line, maxs, options)}" }
68
+ out += ["# #{options[:wrapper_close]}"] if options[:wrapper_close]
69
+
70
+ out
55
71
  end
56
72
 
57
73
  def do_annotations(options = {})
58
74
  return unless routes_exists?
59
75
  existing_text = File.read(routes_file)
60
76
 
61
- if write_contents(existing_text, header(options), options)
77
+ if rewrite_contents_with_header(existing_text, header(options), options)
62
78
  puts "#{routes_file} annotated."
63
79
  end
64
80
  end
65
81
 
66
- def remove_annotations(options={})
82
+ def remove_annotations(_options={})
67
83
  return unless routes_exists?
68
84
  existing_text = File.read(routes_file)
69
85
  content, where_header_found = strip_annotations(existing_text)
70
-
71
- content = strip_on_removal(content, where_header_found)
72
-
73
- if write_contents(existing_text, content, options)
86
+ new_content = strip_on_removal(content, where_header_found)
87
+ if rewrite_contents(existing_text, new_content)
74
88
  puts "Removed annotations from #{routes_file}."
75
89
  end
76
90
  end
77
91
  end
78
92
 
79
- private
93
+ def self.magic_comment_matcher
94
+ Regexp.new(/(^#\s*encoding:.*)|(^# coding:.*)|(^# -\*- coding:.*)|(^# -\*- encoding\s?:.*)|(^#\s*frozen_string_literal:.+)|(^# -\*- frozen_string_literal\s*:.+-\*-)/)
95
+ end
96
+
97
+ # @param [Array<String>] content
98
+ # @return [Array<String>] all found magic comments
99
+ # @return [Array<String>] content without magic comments
100
+ def self.extract_magic_comments_from_array(content_array)
101
+ magic_comments = []
102
+ new_content = []
103
+
104
+ content_array.map do |row|
105
+ if row =~ magic_comment_matcher
106
+ magic_comments << row.strip
107
+ else
108
+ new_content << row
109
+ end
110
+ end
111
+
112
+ [magic_comments, new_content]
113
+ end
80
114
 
81
115
  def self.app_routes_map(options)
82
- routes_map = `rake routes`.split(/\n/, -1)
116
+ routes_map = `rake routes`.chomp("\n").split(/\n/, -1)
83
117
 
84
118
  # In old versions of Rake, the first line of output was the cwd. Not so
85
119
  # much in newer ones. We ditch that line if it exists, and if not, we
@@ -106,7 +140,22 @@ module AnnotateRoutes
106
140
  routes_exists
107
141
  end
108
142
 
109
- def self.write_contents(existing_text, header, options = {})
143
+ # @param [String, Array<String>]
144
+ def self.rewrite_contents(existing_text, new_content)
145
+ # Make sure we end on a trailing newline.
146
+ new_content << '' unless new_content.last == ''
147
+ new_text = new_content.join("\n")
148
+
149
+ if existing_text == new_text
150
+ puts "#{routes_file} unchanged."
151
+ false
152
+ else
153
+ File.open(routes_file, 'wb') { |f| f.puts(new_text) }
154
+ true
155
+ end
156
+ end
157
+
158
+ def self.rewrite_contents_with_header(existing_text, header, options = {})
110
159
  content, where_header_found = strip_annotations(existing_text)
111
160
  new_content = annotate_routes(header, content, where_header_found, options)
112
161
 
@@ -124,9 +173,11 @@ module AnnotateRoutes
124
173
  end
125
174
 
126
175
  def self.annotate_routes(header, content, where_header_found, options = {})
176
+ magic_comments_map, content = extract_magic_comments_from_array(content)
127
177
  if %w(before top).include?(options[:position_in_routes])
128
178
  header = header << '' if content.first != ''
129
- new_content = header + content
179
+ magic_comments_map << '' if magic_comments_map.any?
180
+ new_content = magic_comments_map + header + content
130
181
  else
131
182
  # Ensure we have adequate trailing newlines at the end of the file to
132
183
  # ensure a blank line separating the content from the annotation.
@@ -136,7 +187,7 @@ module AnnotateRoutes
136
187
  # the spacer we put in the first time around.
137
188
  content.shift if where_header_found == :before && content.first == ''
138
189
 
139
- new_content = content + header
190
+ new_content = magic_comments_map + content + header
140
191
  end
141
192
 
142
193
  new_content
@@ -156,7 +207,7 @@ module AnnotateRoutes
156
207
  content.split(/\n/, -1).each_with_index do |line, line_number|
157
208
  if mode == :header && line !~ /\s*#/
158
209
  mode = :content
159
- next unless line == ''
210
+ real_content << line unless line.blank?
160
211
  elsif mode == :content
161
212
  if line =~ /^\s*#\s*== Route.*$/
162
213
  header_found_at = line_number + 1 # index start's at 0
@@ -1,5 +1,5 @@
1
1
  module Annotate
2
2
  def self.version
3
- '2.7.2'
3
+ '2.7.3'
4
4
  end
5
5
  end
@@ -1,8 +1,10 @@
1
+ require 'annotate'
2
+
1
3
  module Annotate
2
4
  module Generators
3
5
  class InstallGenerator < Rails::Generators::Base
4
6
  desc 'Copy annotate_models rakefiles for automatic annotation'
5
- source_root File.expand_path('../templates', __FILE__)
7
+ source_root File.expand_path('templates', __dir__)
6
8
 
7
9
  # copy rake tasks
8
10
  def copy_tasks
@@ -42,6 +42,7 @@ if Rails.env.development?
42
42
  'format_markdown' => 'false',
43
43
  'sort' => 'false',
44
44
  'force' => 'false',
45
+ 'classified_sort' => 'true',
45
46
  'trace' => 'false',
46
47
  'wrapper_open' => nil,
47
48
  'wrapper_close' => nil,
@@ -47,6 +47,7 @@ task annotate_models: :environment do
47
47
  options[:ignore_routes] = ENV.fetch('ignore_routes', nil)
48
48
  options[:hide_limit_column_types] = Annotate.fallback(ENV['hide_limit_column_types'], '')
49
49
  options[:hide_default_column_types] = Annotate.fallback(ENV['hide_default_column_types'], '')
50
+ options[:with_comment] = Annotate.fallback(ENV['with_comment'], '')
50
51
 
51
52
  AnnotateModels.do_annotations(options)
52
53
  end
@@ -8,6 +8,8 @@ task :annotate_routes => :environment do
8
8
  options[:position_in_routes] = Annotate.fallback(ENV['position_in_routes'], ENV['position'])
9
9
  options[:ignore_routes] = Annotate.fallback(ENV['ignore_routes'], nil)
10
10
  options[:require] = ENV['require'] ? ENV['require'].split(',') : []
11
+ options[:wrapper_open] = Annotate.fallback(ENV['wrapper_open'], ENV['wrapper'])
12
+ options[:wrapper_close] = Annotate.fallback(ENV['wrapper_close'], ENV['wrapper'])
11
13
  AnnotateRoutes.do_annotations(options)
12
14
  end
13
15
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: annotate
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.2
4
+ version: 2.7.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Chaffee
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2017-06-02 00:00:00.000000000 Z
15
+ date: 2018-04-21 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: rake
@@ -101,7 +101,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
101
101
  requirements:
102
102
  - - ">="
103
103
  - !ruby/object:Gem::Version
104
- version: 1.9.3
104
+ version: 2.2.0
105
105
  required_rubygems_version: !ruby/object:Gem::Requirement
106
106
  requirements:
107
107
  - - ">="
@@ -109,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
109
  version: '0'
110
110
  requirements: []
111
111
  rubyforge_project: annotate
112
- rubygems_version: 2.6.8
112
+ rubygems_version: 2.7.6
113
113
  signing_key:
114
114
  specification_version: 4
115
115
  summary: Annotates Rails Models, routes, fixtures, and others based on the database