ctran/annotate_models

View on GitHub
lib/annotate/annotate_routes.rb

Summary

Maintainability
B
4 hrs
Test Coverage
# rubocop:disable  Metrics/ModuleLength

# == Annotate Routes
#
# Based on:
#
#
#
# Prepends the output of "rake routes" to the top of your routes.rb file.
# Yes, it's simple but I'm thick and often need a reminder of what my routes
# mean.
#
# Running this task will replace any existing route comment generated by the
# task. Best to back up your routes file before running:
#
# Author:
#  Gavin Montague
#  gavin@leftbrained.co.uk
#
# Released under the same license as Ruby. No Support. No Warranty.
#

require_relative './annotate_routes/helpers'

module AnnotateRoutes
  PREFIX = '== Route Map'.freeze
  PREFIX_MD = '## Route Map'.freeze
  HEADER_ROW = ['Prefix', 'Verb', 'URI Pattern', 'Controller#Action'].freeze

  class << self
    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(header(options), content, header_position, options)
        new_text = new_content.join("\n")

        if rewrite_contents(existing_text, new_text)
          puts "#{routes_file} was annotated."
        else
          puts "#{routes_file} was not changed."
        end
      else
        puts "#{routes_file} could not be found."
      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)
          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."
      end
    end

    private

    def routes_file_exist?
      File.exist?(routes_file)
    end

    def routes_file
      @routes_rb ||= File.join('config', 'routes.rb')
    end

    def header(options = {})
      routes_map = app_routes_map(options)

      magic_comments_map, routes_map = Helpers.extract_magic_comments_from_array(routes_map)

      out = []

      magic_comments_map.each do |magic_comment|
        out << magic_comment
      end
      out << '' if magic_comments_map.any?

      out << comment(options[:wrapper_open]) if options[:wrapper_open]

      out << comment(options[:format_markdown] ? PREFIX_MD : PREFIX) + (options[:timestamp] ? " (Updated #{Time.now.strftime('%Y-%m-%d %H:%M')})" : '')
      out << comment
      return out if routes_map.size.zero?

      maxs = [HEADER_ROW.map(&:size)] + routes_map[1..-1].map { |line| line.split.map(&:size) }

      if options[:format_markdown]
        max = maxs.map(&:max).compact.max

        out << comment(content(HEADER_ROW, maxs, options))
        out << comment(content(['-' * max, '-' * max, '-' * max, '-' * max], maxs, options))
      else
        out << comment(content(routes_map[0], maxs, options))
      end

      out += routes_map[1..-1].map { |line| comment(content(options[:format_markdown] ? line.split(' ') : line, maxs, options)) }
      out << comment(options[:wrapper_close]) if options[:wrapper_close]

      out
    end

    def comment(row = '')
      if row == ''
        '#'
      else
        "# #{row}"
      end
    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)
      if existing_text == new_text
        false
      else
        File.open(routes_file, 'wb') { |f| f.puts(new_text) }
        true
      end
    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

    def app_routes_map(options)
      routes_map = `rake routes`.chomp("\n").split(/\n/, -1)

      # In old versions of Rake, the first line of output was the cwd.  Not so
      # much in newer ones.  We ditch that line if it exists, and if not, we
      # keep the line around.
      routes_map.shift if routes_map.first =~ /^\(in \//

      # Skip routes which match given regex
      # Note: it matches the complete line (route_name, path, controller/action)
      if options[:ignore_routes]
        routes_map.reject! { |line| line =~ /#{options[:ignore_routes]}/ }
      end

      routes_map
    end

    def content(line, maxs, options = {})
      return line.rstrip unless options[:format_markdown]

      line.each_with_index.map do |elem, index|
        min_length = maxs.map { |arr| arr[index] }.max || 0

        sprintf("%-#{min_length}.#{min_length}s", elem.tr('|', '-'))
      end.join(' | ')
    end
  end
end