diff --git a/lib/annotate/annotate_routes.rb b/lib/annotate/annotate_routes.rb index c9a2218ac..b0eb89f45 100644 --- a/lib/annotate/annotate_routes.rb +++ b/lib/annotate/annotate_routes.rb @@ -18,102 +18,28 @@ # Released under the same license as Ruby. No Support. No Warranty. # -require_relative './annotate_routes/helpers' -require_relative './annotate_routes/header_generator' +require_relative './annotate_routes/annotation_processor' +require_relative './annotate_routes/removal_processor' +# This module provides methods for annotating config/routes.rb. module AnnotateRoutes class << self + # @param options [Hash] + # @return [String] def do_annotations(options = {}) - if routes_file_exist? - existing_text = File.read(routes_file) - content, header_position = Helpers.strip_annotations(existing_text) - new_content = annotate_routes(HeaderGenerator.generate(options), content, header_position, options) - new_text = new_content.join("\n") - if rewrite_contents(existing_text, new_text, options[:frozen]) - puts "#{routes_file} was annotated." - else - puts "#{routes_file} was not changed." - end - else - puts "#{routes_file} could not be found." + routes_file = File.join('config', 'routes.rb') + AnnotationProcessor.execute(options, routes_file).tap do |result| + puts result end end - def remove_annotations(options={}) - if routes_file_exist? - existing_text = File.read(routes_file) - content, header_position = Helpers.strip_annotations(existing_text) - new_content = strip_on_removal(content, header_position) - new_text = new_content.join("\n") - if rewrite_contents(existing_text, new_text, options[:frozen]) - puts "Annotations were removed from #{routes_file}." - else - puts "#{routes_file} was not changed (Annotation did not exist)." - end - else - puts "#{routes_file} could not be found." + # @param options [Hash] + # @return [String] + def remove_annotations(options = {}) + routes_file = File.join('config', 'routes.rb') + RemovalProcessor.execute(options, routes_file).tap do |result| + puts result end end - - private - - def routes_file_exist? - File.exist?(routes_file) - end - - def routes_file - @routes_rb ||= File.join('config', 'routes.rb') - end - - def strip_on_removal(content, header_position) - if header_position == :before - content.shift while content.first == '' - elsif header_position == :after - content.pop while content.last == '' - end - - # Make sure we end on a trailing newline. - content << '' unless content.last == '' - - # TODO: If the user buried it in the middle, we should probably see about - # TODO: preserving a single line of space between the content above and - # TODO: below... - content - end - - def rewrite_contents(existing_text, new_text, frozen) - content_changed = (existing_text != new_text) - - if content_changed - abort "annotate error. #{routes_file} needs to be updated, but annotate was run with `--frozen`." if frozen - File.open(routes_file, 'wb') { |f| f.puts(new_text) } - end - - content_changed - end - - def annotate_routes(header, content, header_position, options = {}) - magic_comments_map, content = Helpers.extract_magic_comments_from_array(content) - if %w(before top).include?(options[:position_in_routes]) - header = header << '' if content.first != '' - magic_comments_map << '' if magic_comments_map.any? - new_content = magic_comments_map + header + content - else - # Ensure we have adequate trailing newlines at the end of the file to - # ensure a blank line separating the content from the annotation. - content << '' unless content.last == '' - - # We're moving something from the top of the file to the bottom, so ditch - # the spacer we put in the first time around. - content.shift if header_position == :before && content.first == '' - - new_content = magic_comments_map + content + header - end - - # Make sure we end on a trailing newline. - new_content << '' unless new_content.last == '' - - new_content - end end end diff --git a/lib/annotate/annotate_routes/annotation_processor.rb b/lib/annotate/annotate_routes/annotation_processor.rb new file mode 100644 index 000000000..e22635ca0 --- /dev/null +++ b/lib/annotate/annotate_routes/annotation_processor.rb @@ -0,0 +1,55 @@ +require_relative './base_processor' +require_relative './header_generator' +require_relative './magic_comments_extractor' + +# This module provides methods for annotating config/routes.rb. +module AnnotateRoutes + # This class provides methods for adding annotation to config/routes.rb. + class AnnotationProcessor < BaseProcessor + # @return [String] + def execute + if routes_file_exist? + if update + "#{routes_file} was annotated." + else + "#{routes_file} was not changed." + end + else + "#{routes_file} could not be found." + end + end + + private + + def header + @header ||= HeaderGenerator.generate(options) + end + + def generate_new_content_array(content, header_position) + magic_comments_map, content = MagicCommentsExtractor.execute(content) + if %w[before top].include?(options[:position_in_routes]) + new_content_array = [] + new_content_array += magic_comments_map + new_content_array << '' if magic_comments_map.any? + new_content_array += header + new_content_array << '' if content.first != '' + new_content_array += content + else + # Ensure we have adequate trailing newlines at the end of the file to + # ensure a blank line separating the content from the annotation. + content << '' unless content.last == '' + + # We're moving something from the top of the file to the bottom, so ditch + # the spacer we put in the first time around. + content.shift if header_position == :before && content.first == '' + + new_content_array = magic_comments_map + content + header + end + + # Make sure we end on a trailing newline. + new_content_array << '' unless new_content_array.last == '' + + new_content_array + end + end +end diff --git a/lib/annotate/annotate_routes/base_processor.rb b/lib/annotate/annotate_routes/base_processor.rb new file mode 100644 index 000000000..ec7044951 --- /dev/null +++ b/lib/annotate/annotate_routes/base_processor.rb @@ -0,0 +1,108 @@ +# This module provides methods for annotating config/routes.rb. +module AnnotateRoutes + # This class is abstract class of classes adding and removing annotation to config/routes.rb. + class BaseProcessor + class << self + # @param options [Hash] + # @param routes_file [String] + # @return [String] + def execute(options, routes_file) + new(options, routes_file).execute + end + + private :new + end + + def initialize(options, routes_file) + @options = options + @routes_file = routes_file + end + + # @return [Boolean] + def update + content_changed = content_changed?(new_text) + + abort "annotate error. #{routes_file} needs to be updated, but annotate was run with `--frozen`." if content_changed && frozen? + + if content_changed + write(new_text) + true + else + false + end + end + + def routes_file_exist? + File.exist?(routes_file) + end + + private + + attr_reader :options, :routes_file + + def existing_text + @existing_text ||= File.read(routes_file) + end + + # @return [String] + def new_text + content, header_position = strip_annotations(existing_text) + new_content = generate_new_content_array(content, header_position) + new_content.join("\n") + end + + def write(text) + File.open(routes_file, 'wb') { |f| f.puts(text) } + end + + def content_changed?(new_text) + existing_text != new_text + end + + def frozen? + options[:frozen] + end + + # TODO: write the method doc using ruby rdoc formats + # This method returns an array of 'real_content' and 'header_position'. + # 'header_position' will either be :before, :after, or + # a number. If the number is > 0, the + # annotation was found somewhere in the + # middle of the file. If the number is + # zero, no annotation was found. + def strip_annotations(content) + real_content = [] + mode = :content + header_position = 0 + + content.split(/\n/, -1).each_with_index do |line, line_number| + if mode == :header && line !~ /\s*#/ + mode = :content + real_content << line unless line.blank? + elsif mode == :content + if line =~ /^\s*#\s*== Route.*$/ + header_position = line_number + 1 # index start's at 0 + mode = :header + else + real_content << line + end + end + end + + real_content_and_header_position(real_content, header_position) + end + + def real_content_and_header_position(real_content, header_position) + # By default assume the annotation was found in the middle of the file + + # ... unless we have evidence it was at the beginning ... + return real_content, :before if header_position == 1 + + # ... or that it was at the end. + return real_content, :after if header_position >= real_content.count + + # and the default + [real_content, header_position] + end + end +end diff --git a/lib/annotate/annotate_routes/header_generator.rb b/lib/annotate/annotate_routes/header_generator.rb index b1c93acf7..e8b4c26e6 100644 --- a/lib/annotate/annotate_routes/header_generator.rb +++ b/lib/annotate/annotate_routes/header_generator.rb @@ -1,6 +1,8 @@ -require_relative './helpers' +require_relative './magic_comments_extractor' +# This module provides methods for annotating config/routes.rb. module AnnotateRoutes + # This class processes result of `rake routes` and generate content enbeded in config/routes.rb. class HeaderGenerator PREFIX = '== Route Map'.freeze PREFIX_MD = '## Route Map'.freeze @@ -42,7 +44,7 @@ def initialize(options, routes_map) end def generate - magic_comments_map, contents_without_magic_comments = Helpers.extract_magic_comments_from_array(routes_map) + magic_comments_map, contents_without_magic_comments = MagicCommentsExtractor.execute(routes_map) out = [] diff --git a/lib/annotate/annotate_routes/helpers.rb b/lib/annotate/annotate_routes/helpers.rb deleted file mode 100644 index 1dba65bbe..000000000 --- a/lib/annotate/annotate_routes/helpers.rb +++ /dev/null @@ -1,69 +0,0 @@ -module AnnotateRoutes - module Helpers - MAGIC_COMMENT_MATCHER = Regexp.new(/(^#\s*encoding:.*)|(^# coding:.*)|(^# -\*- coding:.*)|(^# -\*- encoding\s?:.*)|(^#\s*frozen_string_literal:.+)|(^# -\*- frozen_string_literal\s*:.+-\*-)/).freeze - - class << self - # TODO: write the method doc using ruby rdoc formats - # This method returns an array of 'real_content' and 'header_position'. - # 'header_position' will either be :before, :after, or - # a number. If the number is > 0, the - # annotation was found somewhere in the - # middle of the file. If the number is - # zero, no annotation was found. - def strip_annotations(content) - real_content = [] - mode = :content - header_position = 0 - - content.split(/\n/, -1).each_with_index do |line, line_number| - if mode == :header && line !~ /\s*#/ - mode = :content - real_content << line unless line.blank? - elsif mode == :content - if line =~ /^\s*#\s*== Route.*$/ - header_position = line_number + 1 # index start's at 0 - mode = :header - else - real_content << line - end - end - end - - real_content_and_header_position(real_content, header_position) - end - - # @param [Array] content - # @return [Array] all found magic comments - # @return [Array] content without magic comments - def extract_magic_comments_from_array(content_array) - magic_comments = [] - new_content = [] - - content_array.each do |row| - if row =~ MAGIC_COMMENT_MATCHER - magic_comments << row.strip - else - new_content << row - end - end - - [magic_comments, new_content] - end - - private - - def real_content_and_header_position(real_content, header_position) - # By default assume the annotation was found in the middle of the file - - # ... unless we have evidence it was at the beginning ... - return real_content, :before if header_position == 1 - - # ... or that it was at the end. - return real_content, :after if header_position >= real_content.count - - # and the default - return real_content, header_position - end - end - end -end diff --git a/lib/annotate/annotate_routes/magic_comments_extractor.rb b/lib/annotate/annotate_routes/magic_comments_extractor.rb new file mode 100644 index 000000000..8188c4542 --- /dev/null +++ b/lib/annotate/annotate_routes/magic_comments_extractor.rb @@ -0,0 +1,26 @@ +module AnnotateRoutes + # namespace to contain module function to extract magic comments + module MagicCommentsExtractor + MAGIC_COMMENT_MATCHER = Regexp.new(/(^#\s*encoding:.*)|(^# coding:.*)|(^# -\*- coding:.*)|(^# -\*- encoding\s?:.*)|(^#\s*frozen_string_literal:.+)|(^# -\*- frozen_string_literal\s*:.+-\*-)/).freeze + + class << self + # @param [Array] content + # @return [Array] all found magic comments + # @return [Array] content without magic comments + def execute(content_array) + magic_comments = [] + new_content = [] + + content_array.each do |row| + if row =~ MAGIC_COMMENT_MATCHER + magic_comments << row.strip + else + new_content << row + end + end + + [magic_comments, new_content] + end + end + end +end diff --git a/lib/annotate/annotate_routes/removal_processor.rb b/lib/annotate/annotate_routes/removal_processor.rb new file mode 100644 index 000000000..c9aafb255 --- /dev/null +++ b/lib/annotate/annotate_routes/removal_processor.rb @@ -0,0 +1,39 @@ +require_relative './base_processor' + +# This module provides methods for annotating config/routes.rb. +module AnnotateRoutes + # This class provides methods for removing annotation from config/routes.rb. + class RemovalProcessor < BaseProcessor + # @return [String] + def execute + if routes_file_exist? + if update + "Annotations were removed from #{routes_file}." + else + "#{routes_file} was not changed (Annotation did not exist)." + end + else + "#{routes_file} could not be found." + end + end + + private + + def generate_new_content_array(content, header_position) + case header_position + when :before + content.shift while content.first == '' + when :after + content.pop while content.last == '' + end + + # Make sure we end on a trailing newline. + content << '' unless content.last == '' + + # TODO: If the user buried it in the middle, we should probably see about + # TODO: preserving a single line of space between the content above and + # TODO: below... + content + end + end +end