annotate 2.7.5 → 3.2.0
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} +154 -100
- data/RELEASE.md +19 -0
- data/annotate.gemspec +11 -27
- data/bin/annotate +7 -191
- data/lib/annotate/annotate_models/file_patterns.rb +127 -0
- data/lib/annotate/annotate_models.rb +183 -188
- data/lib/annotate/annotate_routes/header_generator.rb +113 -0
- data/lib/annotate/annotate_routes/helpers.rb +69 -0
- data/lib/annotate/annotate_routes.rb +69 -200
- 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/annotate.rb +23 -82
- data/lib/generators/annotate/templates/auto_annotate_models.rake +45 -41
- data/lib/tasks/annotate_models.rake +37 -35
- data/lib/tasks/annotate_models_migrate.rake +17 -4
- data/lib/tasks/annotate_routes.rake +12 -6
- data/potato.md +41 -0
- metadata +25 -17
- data/CHANGELOG.rdoc +0 -220
- data/TODO.rdoc +0 -11
@@ -0,0 +1,69 @@
|
|
1
|
+
module AnnotateRoutes
|
2
|
+
module Helpers
|
3
|
+
MAGIC_COMMENT_MATCHER = Regexp.new(/(^#\s*encoding:.*)|(^# coding:.*)|(^# -\*- coding:.*)|(^# -\*- encoding\s?:.*)|(^#\s*frozen_string_literal:.+)|(^# -\*- frozen_string_literal\s*:.+-\*-)/).freeze
|
4
|
+
|
5
|
+
class << self
|
6
|
+
# TODO: write the method doc using ruby rdoc formats
|
7
|
+
# This method returns an array of 'real_content' and 'header_position'.
|
8
|
+
# 'header_position' will either be :before, :after, or
|
9
|
+
# a number. If the number is > 0, the
|
10
|
+
# annotation was found somewhere in the
|
11
|
+
# middle of the file. If the number is
|
12
|
+
# zero, no annotation was found.
|
13
|
+
def strip_annotations(content)
|
14
|
+
real_content = []
|
15
|
+
mode = :content
|
16
|
+
header_position = 0
|
17
|
+
|
18
|
+
content.split(/\n/, -1).each_with_index do |line, line_number|
|
19
|
+
if mode == :header && line !~ /\s*#/
|
20
|
+
mode = :content
|
21
|
+
real_content << line unless line.blank?
|
22
|
+
elsif mode == :content
|
23
|
+
if line =~ /^\s*#\s*== Route.*$/
|
24
|
+
header_position = line_number + 1 # index start's at 0
|
25
|
+
mode = :header
|
26
|
+
else
|
27
|
+
real_content << line
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
real_content_and_header_position(real_content, header_position)
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param [Array<String>] content
|
36
|
+
# @return [Array<String>] all found magic comments
|
37
|
+
# @return [Array<String>] content without magic comments
|
38
|
+
def extract_magic_comments_from_array(content_array)
|
39
|
+
magic_comments = []
|
40
|
+
new_content = []
|
41
|
+
|
42
|
+
content_array.each do |row|
|
43
|
+
if row =~ MAGIC_COMMENT_MATCHER
|
44
|
+
magic_comments << row.strip
|
45
|
+
else
|
46
|
+
new_content << row
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
[magic_comments, new_content]
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def real_content_and_header_position(real_content, header_position)
|
56
|
+
# By default assume the annotation was found in the middle of the file
|
57
|
+
|
58
|
+
# ... unless we have evidence it was at the beginning ...
|
59
|
+
return real_content, :before if header_position == 1
|
60
|
+
|
61
|
+
# ... or that it was at the end.
|
62
|
+
return real_content, :after if header_position >= real_content.count
|
63
|
+
|
64
|
+
# and the default
|
65
|
+
return real_content, header_position
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
# rubocop:disable Metrics/ModuleLength
|
2
|
-
|
3
1
|
# == Annotate Routes
|
4
2
|
#
|
5
3
|
# Based on:
|
@@ -10,7 +8,7 @@
|
|
10
8
|
# Yes, it's simple but I'm thick and often need a reminder of what my routes
|
11
9
|
# mean.
|
12
10
|
#
|
13
|
-
# Running this task will replace any
|
11
|
+
# Running this task will replace any existing route comment generated by the
|
14
12
|
# task. Best to back up your routes file before running:
|
15
13
|
#
|
16
14
|
# Author:
|
@@ -19,231 +17,102 @@
|
|
19
17
|
#
|
20
18
|
# Released under the same license as Ruby. No Support. No Warranty.
|
21
19
|
#
|
22
|
-
module AnnotateRoutes
|
23
|
-
PREFIX = '== Route Map'.freeze
|
24
|
-
PREFIX_MD = '## Route Map'.freeze
|
25
|
-
HEADER_ROW = ['Prefix', 'Verb', 'URI Pattern', 'Controller#Action']
|
26
|
-
|
27
|
-
class << self
|
28
|
-
def content(line, maxs, options = {})
|
29
|
-
return line.rstrip unless options[:format_markdown]
|
30
|
-
|
31
|
-
line.each_with_index.map do |elem, index|
|
32
|
-
min_length = maxs.map { |arr| arr[index] }.max || 0
|
33
|
-
|
34
|
-
sprintf("%-#{min_length}.#{min_length}s", elem.tr('|', '-'))
|
35
|
-
end.join(' | ')
|
36
|
-
end
|
37
|
-
|
38
|
-
def header(options = {})
|
39
|
-
routes_map = app_routes_map(options)
|
40
|
-
|
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')})" : '')]
|
53
|
-
out += ['#']
|
54
|
-
return out if routes_map.size.zero?
|
55
|
-
|
56
|
-
maxs = [HEADER_ROW.map(&:size)] + routes_map[1..-1].map { |line| line.split.map(&:size) }
|
57
|
-
|
58
|
-
if options[:format_markdown]
|
59
|
-
max = maxs.map(&:max).compact.max
|
60
20
|
|
61
|
-
|
62
|
-
|
63
|
-
else
|
64
|
-
out += ["# #{content(routes_map[0], maxs, options)}"]
|
65
|
-
end
|
66
|
-
|
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
|
71
|
-
end
|
21
|
+
require_relative './annotate_routes/helpers'
|
22
|
+
require_relative './annotate_routes/header_generator'
|
72
23
|
|
24
|
+
module AnnotateRoutes
|
25
|
+
class << self
|
73
26
|
def do_annotations(options = {})
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
27
|
+
if routes_file_exist?
|
28
|
+
existing_text = File.read(routes_file)
|
29
|
+
content, header_position = Helpers.strip_annotations(existing_text)
|
30
|
+
new_content = annotate_routes(HeaderGenerator.generate(options), content, header_position, options)
|
31
|
+
new_text = new_content.join("\n")
|
32
|
+
|
33
|
+
if rewrite_contents(existing_text, new_text)
|
34
|
+
puts "#{routes_file} was annotated."
|
35
|
+
else
|
36
|
+
puts "#{routes_file} was not changed."
|
37
|
+
end
|
38
|
+
else
|
39
|
+
puts "#{routes_file} could not be found."
|
79
40
|
end
|
80
41
|
end
|
81
42
|
|
82
43
|
def remove_annotations(_options={})
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
44
|
+
if routes_file_exist?
|
45
|
+
existing_text = File.read(routes_file)
|
46
|
+
content, header_position = Helpers.strip_annotations(existing_text)
|
47
|
+
new_content = strip_on_removal(content, header_position)
|
48
|
+
new_text = new_content.join("\n")
|
49
|
+
if rewrite_contents(existing_text, new_text)
|
50
|
+
puts "Annotations were removed from #{routes_file}."
|
51
|
+
else
|
52
|
+
puts "#{routes_file} was not changed (Annotation did not exist)."
|
53
|
+
end
|
107
54
|
else
|
108
|
-
|
55
|
+
puts "#{routes_file} could not be found."
|
109
56
|
end
|
110
57
|
end
|
111
58
|
|
112
|
-
|
113
|
-
end
|
114
|
-
|
115
|
-
def self.app_routes_map(options)
|
116
|
-
routes_map = `rake routes`.chomp("\n").split(/\n/, -1)
|
59
|
+
private
|
117
60
|
|
118
|
-
|
119
|
-
|
120
|
-
# keep the line around.
|
121
|
-
routes_map.shift if routes_map.first =~ /^\(in \//
|
122
|
-
|
123
|
-
# Skip routes which match given regex
|
124
|
-
# Note: it matches the complete line (route_name, path, controller/action)
|
125
|
-
if options[:ignore_routes]
|
126
|
-
routes_map.reject! { |line| line =~ /#{options[:ignore_routes]}/ }
|
61
|
+
def routes_file_exist?
|
62
|
+
File.exist?(routes_file)
|
127
63
|
end
|
128
64
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
def self.routes_file
|
133
|
-
@routes_rb ||= File.join('config', 'routes.rb')
|
134
|
-
end
|
135
|
-
|
136
|
-
def self.routes_exists?
|
137
|
-
routes_exists = File.exists?(routes_file)
|
138
|
-
puts "Can't find routes.rb" unless routes_exists
|
139
|
-
|
140
|
-
routes_exists
|
141
|
-
end
|
142
|
-
|
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
|
65
|
+
def routes_file
|
66
|
+
@routes_rb ||= File.join('config', 'routes.rb')
|
155
67
|
end
|
156
|
-
end
|
157
|
-
|
158
|
-
def self.rewrite_contents_with_header(existing_text, header, options = {})
|
159
|
-
content, where_header_found = strip_annotations(existing_text)
|
160
|
-
new_content = annotate_routes(header, content, where_header_found, options)
|
161
68
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
false
|
169
|
-
else
|
170
|
-
File.open(routes_file, 'wb') { |f| f.puts(new_text) }
|
171
|
-
true
|
172
|
-
end
|
173
|
-
end
|
69
|
+
def strip_on_removal(content, header_position)
|
70
|
+
if header_position == :before
|
71
|
+
content.shift while content.first == ''
|
72
|
+
elsif header_position == :after
|
73
|
+
content.pop while content.last == ''
|
74
|
+
end
|
174
75
|
|
175
|
-
|
176
|
-
magic_comments_map, content = extract_magic_comments_from_array(content)
|
177
|
-
if %w(before top).include?(options[:position_in_routes])
|
178
|
-
header = header << '' if content.first != ''
|
179
|
-
magic_comments_map << '' if magic_comments_map.any?
|
180
|
-
new_content = magic_comments_map + header + content
|
181
|
-
else
|
182
|
-
# Ensure we have adequate trailing newlines at the end of the file to
|
183
|
-
# ensure a blank line separating the content from the annotation.
|
76
|
+
# Make sure we end on a trailing newline.
|
184
77
|
content << '' unless content.last == ''
|
185
78
|
|
186
|
-
#
|
187
|
-
#
|
188
|
-
|
189
|
-
|
190
|
-
new_content = magic_comments_map + content + header
|
79
|
+
# TODO: If the user buried it in the middle, we should probably see about
|
80
|
+
# TODO: preserving a single line of space between the content above and
|
81
|
+
# TODO: below...
|
82
|
+
content
|
191
83
|
end
|
192
84
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
# annotation was found somewhere in the
|
200
|
-
# middle of the file. If the number is
|
201
|
-
# zero, no annotation was found.
|
202
|
-
def self.strip_annotations(content)
|
203
|
-
real_content = []
|
204
|
-
mode = :content
|
205
|
-
header_found_at = 0
|
206
|
-
|
207
|
-
content.split(/\n/, -1).each_with_index do |line, line_number|
|
208
|
-
if mode == :header && line !~ /\s*#/
|
209
|
-
mode = :content
|
210
|
-
real_content << line unless line.blank?
|
211
|
-
elsif mode == :content
|
212
|
-
if line =~ /^\s*#\s*== Route.*$/
|
213
|
-
header_found_at = line_number + 1 # index start's at 0
|
214
|
-
mode = :header
|
215
|
-
else
|
216
|
-
real_content << line
|
217
|
-
end
|
85
|
+
def rewrite_contents(existing_text, new_text)
|
86
|
+
if existing_text == new_text
|
87
|
+
false
|
88
|
+
else
|
89
|
+
File.open(routes_file, 'wb') { |f| f.puts(new_text) }
|
90
|
+
true
|
218
91
|
end
|
219
92
|
end
|
220
93
|
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
94
|
+
def annotate_routes(header, content, header_position, options = {})
|
95
|
+
magic_comments_map, content = Helpers.extract_magic_comments_from_array(content)
|
96
|
+
if %w(before top).include?(options[:position_in_routes])
|
97
|
+
header = header << '' if content.first != ''
|
98
|
+
magic_comments_map << '' if magic_comments_map.any?
|
99
|
+
new_content = magic_comments_map + header + content
|
100
|
+
else
|
101
|
+
# Ensure we have adequate trailing newlines at the end of the file to
|
102
|
+
# ensure a blank line separating the content from the annotation.
|
103
|
+
content << '' unless content.last == ''
|
226
104
|
|
227
|
-
|
228
|
-
|
105
|
+
# We're moving something from the top of the file to the bottom, so ditch
|
106
|
+
# the spacer we put in the first time around.
|
107
|
+
content.shift if header_position == :before && content.first == ''
|
229
108
|
|
230
|
-
|
231
|
-
|
109
|
+
new_content = magic_comments_map + content + header
|
110
|
+
end
|
232
111
|
|
233
|
-
|
234
|
-
|
235
|
-
end
|
112
|
+
# Make sure we end on a trailing newline.
|
113
|
+
new_content << '' unless new_content.last == ''
|
236
114
|
|
237
|
-
|
238
|
-
if where_header_found == :before
|
239
|
-
content.shift while content.first == ''
|
240
|
-
elsif where_header_found == :after
|
241
|
-
content.pop while content.last == ''
|
115
|
+
new_content
|
242
116
|
end
|
243
|
-
|
244
|
-
# TODO: If the user buried it in the middle, we should probably see about
|
245
|
-
# TODO: preserving a single line of space between the content above and
|
246
|
-
# TODO: below...
|
247
|
-
content
|
248
117
|
end
|
249
118
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Annotate
|
2
|
+
module Constants
|
3
|
+
TRUE_RE = /^(true|t|yes|y|1)$/i.freeze
|
4
|
+
|
5
|
+
##
|
6
|
+
# The set of available options to customize the behavior of Annotate.
|
7
|
+
#
|
8
|
+
POSITION_OPTIONS = [
|
9
|
+
:position_in_routes, :position_in_class, :position_in_test,
|
10
|
+
:position_in_fixture, :position_in_factory, :position,
|
11
|
+
:position_in_serializer
|
12
|
+
].freeze
|
13
|
+
|
14
|
+
FLAG_OPTIONS = [
|
15
|
+
:show_indexes, :simple_indexes, :include_version, :exclude_tests,
|
16
|
+
:exclude_fixtures, :exclude_factories, :ignore_model_sub_dir,
|
17
|
+
:format_bare, :format_rdoc, :format_yard, :format_markdown, :sort, :force, :frozen,
|
18
|
+
:trace, :timestamp, :exclude_serializers, :classified_sort,
|
19
|
+
:show_foreign_keys, :show_complete_foreign_keys,
|
20
|
+
:exclude_scaffolds, :exclude_controllers, :exclude_helpers,
|
21
|
+
:exclude_sti_subclasses, :ignore_unknown_models, :with_comment
|
22
|
+
].freeze
|
23
|
+
|
24
|
+
OTHER_OPTIONS = [
|
25
|
+
:additional_file_patterns, :ignore_columns, :skip_on_db_migrate, :wrapper_open, :wrapper_close,
|
26
|
+
:wrapper, :routes, :models, :hide_limit_column_types, :hide_default_column_types,
|
27
|
+
:ignore_routes, :active_admin
|
28
|
+
].freeze
|
29
|
+
|
30
|
+
PATH_OPTIONS = [
|
31
|
+
:require, :model_dir, :root_dir
|
32
|
+
].freeze
|
33
|
+
|
34
|
+
ALL_ANNOTATE_OPTIONS = [
|
35
|
+
POSITION_OPTIONS, FLAG_OPTIONS, OTHER_OPTIONS, PATH_OPTIONS
|
36
|
+
].freeze
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Annotate
|
2
|
+
# Class for holding helper methods. Done to make lib/annotate.rb less bloated.
|
3
|
+
class Helpers
|
4
|
+
class << self
|
5
|
+
def skip_on_migration?
|
6
|
+
ENV['ANNOTATE_SKIP_ON_DB_MIGRATE'] =~ Constants::TRUE_RE || ENV['skip_on_db_migrate'] =~ Constants::TRUE_RE
|
7
|
+
end
|
8
|
+
|
9
|
+
def include_routes?
|
10
|
+
ENV['routes'] =~ Constants::TRUE_RE
|
11
|
+
end
|
12
|
+
|
13
|
+
def include_models?
|
14
|
+
ENV['models'] =~ Constants::TRUE_RE
|
15
|
+
end
|
16
|
+
|
17
|
+
def true?(val)
|
18
|
+
val.present? && Constants::TRUE_RE.match?(val)
|
19
|
+
end
|
20
|
+
|
21
|
+
def fallback(*args)
|
22
|
+
args.detect(&:present?)
|
23
|
+
end
|
24
|
+
|
25
|
+
def reset_options(options)
|
26
|
+
options.flatten.each { |key| ENV[key.to_s] = nil }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|