codeclimate/codeclimate

View on GitHub
lib/cc/config/prepare.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
95%
module CC
  class Config
    class Prepare
      attr_reader :fetch

      def self.from_data(data)
        if data.present?
          fetch = Fetch.from_data(data.fetch("fetch", []))

          new(fetch: fetch)
        else
          new
        end
      end

      def initialize(fetch: Fetch.new)
        @fetch = fetch
      end

      def merge(other)
        Prepare.new(fetch: fetch.merge(other.fetch))
      end

      class Fetch
        def self.from_data(data)
          new(data.map { |d| Entry.from_data(d) })
        end

        def initialize(entries = [])
          @entries = Set.new(entries)
        end

        def each(&block)
          entries.each(&block)
        end

        def paths
          entries.map(&:path)
        end

        def merge(other)
          Fetch.new(each.to_a | other.each.to_a)
        end

        private

        attr_reader :entries

        class Entry
          attr_reader :url, :path

          def self.from_data(data)
            case data
            when String then new(data)
            when Hash then new(data.fetch("url"), data["path"])
            end
          end

          def initialize(url, path = nil)
            @url = url
            @path = path || url.split("/").last

            validate_path!
          end

          # Useful in specs
          def ==(other)
            other.is_a?(self.class) &&
              other.url == url &&
              other.path == path
          end

          private

          # Duplicate a validation which has security implication. This should
          # always be caught upstream, so raising loudly is fine.
          def validate_path!
            if path.blank?
              raise ArgumentError, "path cannot be be blank"
            end

            pathname = Pathname.new(path)

            if pathname.absolute?
              raise ArgumentError, "path cannot be absolute: #{path}"
            end

            if pathname.cleanpath.to_s != pathname.to_s || path.include?("..")
              raise ArgumentError, "path cannot point outside the current directory: #{path}"
            end
          end
        end
      end
    end
  end
end