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.
@@ -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 exising route comment generated by the
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
- out += ["# #{content(HEADER_ROW, maxs, options)}"]
62
- out += ["# #{content(['-' * max, '-' * max, '-' * max, '-' * max], maxs, options)}"]
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
- return unless routes_exists?
75
- existing_text = File.read(routes_file)
76
-
77
- if rewrite_contents_with_header(existing_text, header(options), options)
78
- puts "#{routes_file} annotated."
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
- return unless routes_exists?
84
- existing_text = File.read(routes_file)
85
- content, where_header_found = strip_annotations(existing_text)
86
- new_content = strip_on_removal(content, where_header_found)
87
- if rewrite_contents(existing_text, new_content)
88
- puts "Removed annotations from #{routes_file}."
89
- end
90
- end
91
- end
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
- new_content << row
55
+ puts "#{routes_file} could not be found."
109
56
  end
110
57
  end
111
58
 
112
- [magic_comments, new_content]
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
- # In old versions of Rake, the first line of output was the cwd. Not so
119
- # much in newer ones. We ditch that line if it exists, and if not, we
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
- 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
-
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
- # Make sure we end on a trailing newline.
163
- new_content << '' unless new_content.last == ''
164
- new_text = new_content.join("\n")
165
-
166
- if existing_text == new_text
167
- puts "#{routes_file} unchanged."
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
- 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.
76
+ # Make sure we end on a trailing newline.
184
77
  content << '' unless content.last == ''
185
78
 
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
-
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
- new_content
194
- end
195
-
196
- # TODO: write the method doc using ruby rdoc formats
197
- # where_header_found => This will either be :before, :after, or
198
- # a number. If the number is > 0, the
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
- where_header_found(real_content, header_found_at)
222
- end
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
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
- # ... unless we have evidence it was at the beginning ...
228
- return real_content, :before if header_found_at == 1
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
- # ... or that it was at the end.
231
- return real_content, :after if header_found_at >= real_content.count
109
+ new_content = magic_comments_map + content + header
110
+ end
232
111
 
233
- # and the default
234
- return real_content, header_found_at
235
- end
112
+ # Make sure we end on a trailing newline.
113
+ new_content << '' unless new_content.last == ''
236
114
 
237
- def self.strip_on_removal(content, where_header_found)
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