annotate 2.6.3 → 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,113 @@
1
+ require_relative './helpers'
2
+
3
+ module AnnotateRoutes
4
+ class HeaderGenerator
5
+ PREFIX = '== Route Map'.freeze
6
+ PREFIX_MD = '## Route Map'.freeze
7
+ HEADER_ROW = ['Prefix', 'Verb', 'URI Pattern', 'Controller#Action'].freeze
8
+
9
+ class << self
10
+ def generate(options = {})
11
+ new(options, routes_map(options)).generate
12
+ end
13
+
14
+ private :new
15
+
16
+ private
17
+
18
+ def routes_map(options)
19
+ result = `rake routes`.chomp("\n").split(/\n/, -1)
20
+
21
+ # In old versions of Rake, the first line of output was the cwd. Not so
22
+ # much in newer ones. We ditch that line if it exists, and if not, we
23
+ # keep the line around.
24
+ result.shift if result.first =~ %r{^\(in \/}
25
+
26
+ ignore_routes = options[:ignore_routes]
27
+ regexp_for_ignoring_routes = ignore_routes ? /#{ignore_routes}/ : nil
28
+
29
+ # Skip routes which match given regex
30
+ # Note: it matches the complete line (route_name, path, controller/action)
31
+ if regexp_for_ignoring_routes
32
+ result.reject { |line| line =~ regexp_for_ignoring_routes }
33
+ else
34
+ result
35
+ end
36
+ end
37
+ end
38
+
39
+ def initialize(options, routes_map)
40
+ @options = options
41
+ @routes_map = routes_map
42
+ end
43
+
44
+ def generate
45
+ magic_comments_map, contents_without_magic_comments = Helpers.extract_magic_comments_from_array(routes_map)
46
+
47
+ out = []
48
+
49
+ magic_comments_map.each do |magic_comment|
50
+ out << magic_comment
51
+ end
52
+ out << '' if magic_comments_map.any?
53
+
54
+ out << comment(options[:wrapper_open]) if options[:wrapper_open]
55
+
56
+ out << comment(markdown? ? PREFIX_MD : PREFIX) + timestamp_if_required
57
+ out << comment
58
+ return out if contents_without_magic_comments.size.zero?
59
+
60
+ maxs = [HEADER_ROW.map(&:size)] + contents_without_magic_comments[1..-1].map { |line| line.split.map(&:size) }
61
+
62
+ if markdown?
63
+ max = maxs.map(&:max).compact.max
64
+
65
+ out << comment(content(HEADER_ROW, maxs))
66
+ out << comment(content(['-' * max, '-' * max, '-' * max, '-' * max], maxs))
67
+ else
68
+ out << comment(content(contents_without_magic_comments[0], maxs))
69
+ end
70
+
71
+ out += contents_without_magic_comments[1..-1].map { |line| comment(content(markdown? ? line.split(' ') : line, maxs)) }
72
+ out << comment(options[:wrapper_close]) if options[:wrapper_close]
73
+
74
+ out
75
+ end
76
+
77
+ private
78
+
79
+ attr_reader :options, :routes_map
80
+
81
+ def comment(row = '')
82
+ if row == ''
83
+ '#'
84
+ else
85
+ "# #{row}"
86
+ end
87
+ end
88
+
89
+ def content(line, maxs)
90
+ return line.rstrip unless markdown?
91
+
92
+ line.each_with_index.map { |elem, index| format_line_element(elem, maxs, index) }.join(' | ')
93
+ end
94
+
95
+ def format_line_element(elem, maxs, index)
96
+ min_length = maxs.map { |arr| arr[index] }.max || 0
97
+ format("%-#{min_length}.#{min_length}s", elem.tr('|', '-'))
98
+ end
99
+
100
+ def markdown?
101
+ options[:format_markdown]
102
+ end
103
+
104
+ def timestamp_if_required(time = Time.now)
105
+ if options[:timestamp]
106
+ time_formatted = time.strftime('%Y-%m-%d %H:%M')
107
+ " (Updated #{time_formatted})"
108
+ else
109
+ ''
110
+ end
111
+ end
112
+ end
113
+ 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
@@ -8,7 +8,7 @@
8
8
  # Yes, it's simple but I'm thick and often need a reminder of what my routes
9
9
  # mean.
10
10
  #
11
- # 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
12
12
  # task. Best to back up your routes file before running:
13
13
  #
14
14
  # Author:
@@ -17,131 +17,102 @@
17
17
  #
18
18
  # Released under the same license as Ruby. No Support. No Warranty.
19
19
  #
20
- module AnnotateRoutes
21
- PREFIX = "# == Route Map"
22
-
23
- def self.do_annotations(options={})
24
- return unless(routes_exists?)
25
-
26
- position_after = options[:position_in_routes] != 'before'
27
-
28
- routes_map = `rake routes`.split(/\n/, -1)
29
20
 
30
- # In old versions of Rake, the first line of output was the cwd. Not so
31
- # much in newer ones. We ditch that line if it exists, and if not, we
32
- # keep the line around.
33
- routes_map.shift if(routes_map.first =~ /^\(in \//)
21
+ require_relative './annotate_routes/helpers'
22
+ require_relative './annotate_routes/header_generator'
34
23
 
35
- header = [
36
- "#{PREFIX} (Updated #{Time.now.strftime("%Y-%m-%d %H:%M")})",
37
- "#"
38
- ] + routes_map.map { |line| "# #{line}".rstrip }
39
-
40
- (content, where_header_found) = strip_annotations(File.read(routes_file))
41
- changed = where_header_found != 0 # This will either be :before, :after, or
42
- # a number. If the number is > 0, the
43
- # annotation was found somewhere in the
44
- # middle of the file. If the number is
45
- # zero, no annotation was found.
46
-
47
- if(position_after)
48
- # Ensure we have adequate trailing newlines at the end of the file to
49
- # ensure a blank line separating the content from the annotation.
50
- content << '' if(content.last != '')
51
-
52
- # We're moving something from the top of the file to the bottom, so ditch
53
- # the spacer we put in the first time around.
54
- if(changed && where_header_found == :before)
55
- content.shift if(content.first == '')
24
+ module AnnotateRoutes
25
+ class << self
26
+ def do_annotations(options = {})
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."
56
40
  end
57
- else
58
- header = header << '' if(content.first != '' || changed)
59
41
  end
60
42
 
61
- content = position_after ? (content + header) : header + content
62
-
63
- write_contents(content)
64
-
65
- puts "Route file annotated."
66
- end
67
-
68
- def self.remove_annotations(options={})
69
- return unless(routes_exists?)
70
-
71
- (content, where_header_found) = strip_annotations(File.read(routes_file))
72
-
73
- content = strip_on_removal(content, where_header_found)
74
-
75
- write_contents(content)
43
+ def remove_annotations(_options={})
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
54
+ else
55
+ puts "#{routes_file} could not be found."
56
+ end
57
+ end
76
58
 
77
- puts "Removed annotations from routes file."
78
- end
59
+ private
79
60
 
80
- protected
61
+ def routes_file_exist?
62
+ File.exist?(routes_file)
63
+ end
81
64
 
82
- def self.routes_file
83
- @routes_rb ||= File.join("config", "routes.rb")
84
- end
65
+ def routes_file
66
+ @routes_rb ||= File.join('config', 'routes.rb')
67
+ end
85
68
 
86
- def self.routes_exists?
87
- routes_exists = File.exists?(routes_file)
88
- puts "Can`t find routes.rb" if(!routes_exists)
89
- return routes_exists
90
- 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
91
75
 
92
- def self.write_contents(content)
93
- content << '' unless(content.last == '') # Make sure we end on a trailing
94
- # newline.
76
+ # Make sure we end on a trailing newline.
77
+ content << '' unless content.last == ''
95
78
 
96
- File.open(routes_file, "wb") { |f| f.puts(content.join("\n")) }
97
- end
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
83
+ end
98
84
 
99
- def self.strip_annotations(content)
100
- real_content = []
101
- mode = :content
102
- line_number = 0
103
- header_found_at = 0
104
- content.split(/\n/, -1).each do |line|
105
- line_number += 1
106
- begin
107
- if(mode == :header)
108
- if(line !~ /\s*#/)
109
- mode = :content
110
- raise unless (line == '')
111
- end
112
- elsif(mode == :content)
113
- if(line =~ /^\s*#\s*== Route.*$/)
114
- header_found_at = line_number
115
- mode = :header
116
- else
117
- real_content << line
118
- end
119
- end
120
- rescue
121
- retry
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
122
91
  end
123
92
  end
124
- content_lines = real_content.count
125
93
 
126
- # By default assume the annotation was found in the middle of the file...
127
- where_header_found = header_found_at
128
- # ... unless we have evidence it was at the beginning ...
129
- where_header_found = :before if(header_found_at == 1)
130
- # ... or that it was at the end.
131
- where_header_found = :after if(header_found_at >= content_lines)
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 == ''
104
+
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 == ''
108
+
109
+ new_content = magic_comments_map + content + header
110
+ end
132
111
 
133
- return real_content, where_header_found
134
- end
112
+ # Make sure we end on a trailing newline.
113
+ new_content << '' unless new_content.last == ''
135
114
 
136
- def self.strip_on_removal(content, where_header_found)
137
- if(where_header_found == :before)
138
- content.shift while(content.first == '')
139
- elsif(where_header_found == :after)
140
- content.pop while(content.last == '')
115
+ new_content
141
116
  end
142
- # TODO: If the user buried it in the middle, we should probably see about
143
- # TODO: preserving a single line of space between the content above and
144
- # TODO: below...
145
- return content
146
117
  end
147
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