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
@@ -10,7 +10,7 @@
|
|
10
10
|
# Yes, it's simple but I'm thick and often need a reminder of what my routes
|
11
11
|
# mean.
|
12
12
|
#
|
13
|
-
# Running this task will replace any
|
13
|
+
# Running this task will replace any existing route comment generated by the
|
14
14
|
# task. Best to back up your routes file before running:
|
15
15
|
#
|
16
16
|
# Author:
|
@@ -19,26 +19,62 @@
|
|
19
19
|
#
|
20
20
|
# Released under the same license as Ruby. No Support. No Warranty.
|
21
21
|
#
|
22
|
+
|
23
|
+
require_relative './annotate_routes/helpers'
|
24
|
+
|
22
25
|
module AnnotateRoutes
|
23
26
|
PREFIX = '== Route Map'.freeze
|
24
27
|
PREFIX_MD = '## Route Map'.freeze
|
25
|
-
HEADER_ROW = ['Prefix', 'Verb', 'URI Pattern', 'Controller#Action']
|
28
|
+
HEADER_ROW = ['Prefix', 'Verb', 'URI Pattern', 'Controller#Action'].freeze
|
26
29
|
|
27
30
|
class << self
|
28
|
-
def
|
29
|
-
|
31
|
+
def do_annotations(options = {})
|
32
|
+
if routes_file_exist?
|
33
|
+
existing_text = File.read(routes_file)
|
34
|
+
content, header_position = Helpers.strip_annotations(existing_text)
|
35
|
+
new_content = annotate_routes(header(options), content, header_position, options)
|
36
|
+
new_text = new_content.join("\n")
|
37
|
+
|
38
|
+
if rewrite_contents(existing_text, new_text)
|
39
|
+
puts "#{routes_file} was annotated."
|
40
|
+
else
|
41
|
+
puts "#{routes_file} was not changed."
|
42
|
+
end
|
43
|
+
else
|
44
|
+
puts "#{routes_file} could not be found."
|
45
|
+
end
|
46
|
+
end
|
30
47
|
|
31
|
-
|
32
|
-
|
48
|
+
def remove_annotations(_options={})
|
49
|
+
if routes_file_exist?
|
50
|
+
existing_text = File.read(routes_file)
|
51
|
+
content, header_position = Helpers.strip_annotations(existing_text)
|
52
|
+
new_content = strip_on_removal(content, header_position)
|
53
|
+
new_text = new_content.join("\n")
|
54
|
+
if rewrite_contents(existing_text, new_text)
|
55
|
+
puts "Annotations were removed from #{routes_file}."
|
56
|
+
else
|
57
|
+
puts "#{routes_file} was not changed (Annotation did not exist)."
|
58
|
+
end
|
59
|
+
else
|
60
|
+
puts "#{routes_file} could not be found."
|
61
|
+
end
|
62
|
+
end
|
33
63
|
|
34
|
-
|
35
|
-
|
64
|
+
private
|
65
|
+
|
66
|
+
def routes_file_exist?
|
67
|
+
File.exist?(routes_file)
|
68
|
+
end
|
69
|
+
|
70
|
+
def routes_file
|
71
|
+
@routes_rb ||= File.join('config', 'routes.rb')
|
36
72
|
end
|
37
73
|
|
38
74
|
def header(options = {})
|
39
75
|
routes_map = app_routes_map(options)
|
40
76
|
|
41
|
-
magic_comments_map, routes_map = extract_magic_comments_from_array(routes_map)
|
77
|
+
magic_comments_map, routes_map = Helpers.extract_magic_comments_from_array(routes_map)
|
42
78
|
|
43
79
|
out = []
|
44
80
|
|
@@ -47,10 +83,10 @@ module AnnotateRoutes
|
|
47
83
|
end
|
48
84
|
out << '' if magic_comments_map.any?
|
49
85
|
|
50
|
-
out
|
86
|
+
out << comment(options[:wrapper_open]) if options[:wrapper_open]
|
51
87
|
|
52
|
-
out
|
53
|
-
out
|
88
|
+
out << comment(options[:format_markdown] ? PREFIX_MD : PREFIX) + (options[:timestamp] ? " (Updated #{Time.now.strftime('%Y-%m-%d %H:%M')})" : '')
|
89
|
+
out << comment
|
54
90
|
return out if routes_map.size.zero?
|
55
91
|
|
56
92
|
maxs = [HEADER_ROW.map(&:size)] + routes_map[1..-1].map { |line| line.split.map(&:size) }
|
@@ -58,192 +94,100 @@ module AnnotateRoutes
|
|
58
94
|
if options[:format_markdown]
|
59
95
|
max = maxs.map(&:max).compact.max
|
60
96
|
|
61
|
-
out
|
62
|
-
out
|
97
|
+
out << comment(content(HEADER_ROW, maxs, options))
|
98
|
+
out << comment(content(['-' * max, '-' * max, '-' * max, '-' * max], maxs, options))
|
63
99
|
else
|
64
|
-
out
|
100
|
+
out << comment(content(routes_map[0], maxs, options))
|
65
101
|
end
|
66
102
|
|
67
|
-
out += routes_map[1..-1].map { |line|
|
68
|
-
out
|
103
|
+
out += routes_map[1..-1].map { |line| comment(content(options[:format_markdown] ? line.split(' ') : line, maxs, options)) }
|
104
|
+
out << comment(options[:wrapper_close]) if options[:wrapper_close]
|
69
105
|
|
70
106
|
out
|
71
107
|
end
|
72
108
|
|
73
|
-
def
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
puts "#{routes_file} annotated."
|
109
|
+
def comment(row = '')
|
110
|
+
if row == ''
|
111
|
+
'#'
|
112
|
+
else
|
113
|
+
"# #{row}"
|
79
114
|
end
|
80
115
|
end
|
81
116
|
|
82
|
-
def
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
if rewrite_contents(existing_text, new_content)
|
88
|
-
puts "Removed annotations from #{routes_file}."
|
117
|
+
def strip_on_removal(content, header_position)
|
118
|
+
if header_position == :before
|
119
|
+
content.shift while content.first == ''
|
120
|
+
elsif header_position == :after
|
121
|
+
content.pop while content.last == ''
|
89
122
|
end
|
90
|
-
end
|
91
|
-
end
|
92
123
|
|
93
|
-
|
94
|
-
|
95
|
-
end
|
124
|
+
# Make sure we end on a trailing newline.
|
125
|
+
content << '' unless content.last == ''
|
96
126
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
new_content = []
|
127
|
+
# TODO: If the user buried it in the middle, we should probably see about
|
128
|
+
# TODO: preserving a single line of space between the content above and
|
129
|
+
# TODO: below...
|
130
|
+
content
|
131
|
+
end
|
103
132
|
|
104
|
-
|
105
|
-
if
|
106
|
-
|
133
|
+
def rewrite_contents(existing_text, new_text)
|
134
|
+
if existing_text == new_text
|
135
|
+
false
|
107
136
|
else
|
108
|
-
|
137
|
+
File.open(routes_file, 'wb') { |f| f.puts(new_text) }
|
138
|
+
true
|
109
139
|
end
|
110
140
|
end
|
111
141
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
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]}/ }
|
127
|
-
end
|
128
|
-
|
129
|
-
routes_map
|
130
|
-
end
|
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
|
+
def annotate_routes(header, content, header_position, options = {})
|
143
|
+
magic_comments_map, content = Helpers.extract_magic_comments_from_array(content)
|
144
|
+
if %w(before top).include?(options[:position_in_routes])
|
145
|
+
header = header << '' if content.first != ''
|
146
|
+
magic_comments_map << '' if magic_comments_map.any?
|
147
|
+
new_content = magic_comments_map + header + content
|
148
|
+
else
|
149
|
+
# Ensure we have adequate trailing newlines at the end of the file to
|
150
|
+
# ensure a blank line separating the content from the annotation.
|
151
|
+
content << '' unless content.last == ''
|
142
152
|
|
143
|
-
|
144
|
-
|
145
|
-
|
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
|
153
|
+
# We're moving something from the top of the file to the bottom, so ditch
|
154
|
+
# the spacer we put in the first time around.
|
155
|
+
content.shift if header_position == :before && content.first == ''
|
157
156
|
|
158
|
-
|
159
|
-
|
160
|
-
new_content = annotate_routes(header, content, where_header_found, options)
|
157
|
+
new_content = magic_comments_map + content + header
|
158
|
+
end
|
161
159
|
|
162
|
-
|
163
|
-
|
164
|
-
new_text = new_content.join("\n")
|
160
|
+
# Make sure we end on a trailing newline.
|
161
|
+
new_content << '' unless new_content.last == ''
|
165
162
|
|
166
|
-
|
167
|
-
puts "#{routes_file} unchanged."
|
168
|
-
false
|
169
|
-
else
|
170
|
-
File.open(routes_file, 'wb') { |f| f.puts(new_text) }
|
171
|
-
true
|
163
|
+
new_content
|
172
164
|
end
|
173
|
-
end
|
174
|
-
|
175
|
-
def self.annotate_routes(header, content, where_header_found, options = {})
|
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.
|
184
|
-
content << '' unless content.last == ''
|
185
|
-
|
186
|
-
# We're moving something from the top of the file to the bottom, so ditch
|
187
|
-
# the spacer we put in the first time around.
|
188
|
-
content.shift if where_header_found == :before && content.first == ''
|
189
165
|
|
190
|
-
|
191
|
-
|
166
|
+
def app_routes_map(options)
|
167
|
+
routes_map = `rake routes`.chomp("\n").split(/\n/, -1)
|
192
168
|
|
193
|
-
|
194
|
-
|
169
|
+
# In old versions of Rake, the first line of output was the cwd. Not so
|
170
|
+
# much in newer ones. We ditch that line if it exists, and if not, we
|
171
|
+
# keep the line around.
|
172
|
+
routes_map.shift if routes_map.first =~ /^\(in \//
|
195
173
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
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
|
174
|
+
# Skip routes which match given regex
|
175
|
+
# Note: it matches the complete line (route_name, path, controller/action)
|
176
|
+
if options[:ignore_routes]
|
177
|
+
routes_map.reject! { |line| line =~ /#{options[:ignore_routes]}/ }
|
218
178
|
end
|
219
|
-
end
|
220
179
|
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
def self.where_header_found(real_content, header_found_at)
|
225
|
-
# By default assume the annotation was found in the middle of the file
|
226
|
-
|
227
|
-
# ... unless we have evidence it was at the beginning ...
|
228
|
-
return real_content, :before if header_found_at == 1
|
180
|
+
routes_map
|
181
|
+
end
|
229
182
|
|
230
|
-
|
231
|
-
|
183
|
+
def content(line, maxs, options = {})
|
184
|
+
return line.rstrip unless options[:format_markdown]
|
232
185
|
|
233
|
-
|
234
|
-
|
235
|
-
end
|
186
|
+
line.each_with_index.map do |elem, index|
|
187
|
+
min_length = maxs.map { |arr| arr[index] }.max || 0
|
236
188
|
|
237
|
-
|
238
|
-
|
239
|
-
content.shift while content.first == ''
|
240
|
-
elsif where_header_found == :after
|
241
|
-
content.pop while content.last == ''
|
189
|
+
sprintf("%-#{min_length}.#{min_length}s", elem.tr('|', '-'))
|
190
|
+
end.join(' | ')
|
242
191
|
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
192
|
end
|
249
193
|
end
|
@@ -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
|
@@ -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
|