shageman/cobradeps

View on GitHub
lib/cobradeps/gemfile_scraper.rb

Summary

Maintainability
A
45 mins
Test Coverage
module Cbradeps
  class GemfileScraper
    def initialize(root_path)
      @root_path = root_path
    end

    def name
      Pathname.new(@root_path).basename.to_s
    end

    def to_s
      cobra_dependencies
    end

    def cobra_dependencies
      dirdep = direct_dependencies
      transitive_cobra_dependencies.select do |dep|
        dirdep.include?(dep[:name]) || dep[:options][:direct]
      end
    end

    private

    def transitive_cobra_dependencies
      gem_dependencies.inject([]) do |memo, dep|
        if !!dep[:options][:path]
          absolute_dep = dep.clone
          absolute_dep[:options][:path] = File.expand_path(File.join(@root_path, dep[:options][:path]))
          memo << dep
        end
        memo
      end
    end

    def raw_gemspec
      path = File.expand_path(File.join(@root_path, "#{underscore(name)}.gemspec"))
      File.exist?(path) ? File.read(path) : ""
    end

    def direct_dependencies
      raw_gemspec.split("\n").inject([]) do |memo, line|
        match = line.match(/add_(?:development_)?dependency\s+["']([^'"]+)["']/)
        memo << match[1] if match
        memo
      end
    end

    def raw_gemfile
      path = File.expand_path(File.join(@root_path, "Gemfile"))
      File.read(path)
    end

    def gem_dependencies
      in_path_block_dir = nil
      raw_gemfile.split("\n").inject([]) do |memo, line|
        if in_path_block_dir
          if match = line.match(/gem\s+["']([^'"]+)["'](.*)/)
            memo << {name: match[1], options: {path: "#{in_path_block_dir}/#{match[1]}", direct: true}}
          elsif line.match(/end/)
            in_path_block_dir = nil
          end
        else
          if match = line.match(/gem\s+["']([^'"]+)["'](.*)/)
            memo << {name: match[1], options: NonBlockOptionParser.new(match[2]).parse}
          elsif match = line.match(/path\s+[\"\'\:]?([^\"\']*)[\"\']?\s+do/)
            in_path_block_dir = match[1]
          end
        end
        memo
      end
    end

    def underscore(string)
      string.gsub(/::/, '/').
          gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
          gsub(/([a-z\d])([A-Z])/, '\1_\2').
          tr("-", "_").
          downcase
    end

    class NonBlockOptionParser
      def initialize(options)
        @options = options
      end

      def parse
        {}.merge(path).merge(direct)
      end

      private

      def path
        match = @options.match(/path(?:\s*=>|:)\s+["']([^'"]+)["']/)
        match ? {path: match[1]} : {}
      end

      def direct
        match = @options.match(/group(?:\s*=>|:)\s+:direct/)
        match ? {direct: true} : {}
      end
    end
  end
end