annotate 2.6.3 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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