1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
|
require 'rubygems'
module Librarian
class Dependency
class Requirement
def initialize(*args)
args = initialize_normalize_args(args)
self.backing = Gem::Requirement.create(args)
end
def to_gem_requirement
backing
end
def satisfied_by?(version)
to_gem_requirement.satisfied_by?(version.to_gem_version)
end
def ==(other)
to_gem_requirement == other.to_gem_requirement
end
alias :eql? :==
def hash
self.to_s.hash
end
def to_s
to_gem_requirement.to_s
end
def inspect
"#<#{self.class} #{to_s}>"
end
COMPATS_TABLE = {
%w(= = ) => lambda{|s, o| s == o},
%w(= !=) => lambda{|s, o| s != o},
%w(= > ) => lambda{|s, o| s > o},
%w(= < ) => lambda{|s, o| s < o},
%w(= >=) => lambda{|s, o| s >= o},
%w(= <=) => lambda{|s, o| s <= o},
%w(= ~>) => lambda{|s, o| s >= o && s.release < o.bump},
%w(!= !=) => true,
%w(!= > ) => true,
%w(!= < ) => true,
%w(!= >=) => true,
%w(!= <=) => true,
%w(!= ~>) => true,
%w(> > ) => true,
%w(> < ) => lambda{|s, o| s < o},
%w(> >=) => true,
%w(> <=) => lambda{|s, o| s < o},
%w(> ~>) => lambda{|s, o| s < o.bump},
%w(< < ) => true,
%w(< >=) => lambda{|s, o| s > o},
%w(< <=) => true,
%w(< ~>) => lambda{|s, o| s > o},
%w(>= >=) => true,
%w(>= <=) => lambda{|s, o| s <= o},
%w(>= ~>) => lambda{|s, o| s < o.bump},
%w(<= <=) => true,
%w(<= ~>) => lambda{|s, o| s >= o},
%w(~> ~>) => lambda{|s, o| s < o.bump && s.bump > o},
}
def consistent_with?(other)
sgreq, ogreq = to_gem_requirement, other.to_gem_requirement
sreqs, oreqs = sgreq.requirements, ogreq.requirements
sreqs.all? do |sreq|
oreqs.all? do |oreq|
compatible?(sreq, oreq)
end
end
end
def inconsistent_with?(other)
!consistent_with?(other)
end
protected
attr_accessor :backing
private
def initialize_normalize_args(args)
args.map do |arg|
arg = arg.backing if self.class === arg
case arg
when nil
nil
when Array
arg.map { |item| parse(item) }
when String
parse(arg)
else
# Gem::Requirement, convert to string (ie. =1.0) so we can concat later
# Gem::Requirements can not be concatenated
arg.requirements.map{|x,y| "#{x}#{y}"}
end
end.flatten
end
# build an array if the argument is a string defining a range
# or a ~> 1.0 type version if string is 1.x
def parse(arg)
return nil if arg.nil?
match = range_requirement(arg)
return [match[1], match[2]] if match
match = pessimistic_requirement(arg)
return "~> #{match[1]}.0" if match
arg
end
def compatible?(a, b)
a, b = b, a unless COMPATS_TABLE.include?([a.first, b.first])
r = COMPATS_TABLE[[a.first, b.first]]
r = r.call(a.last, b.last) if r.respond_to?(:call)
r
end
# A version range: >=1.0 <2.0
def range_requirement(arg)
arg.match(/(>=? ?\d+(?:\.\d+){0,2})\s*(<=? ?\d+(?:\.\d+){0,2})/)
end
# A string with .x: 1.x, 2.1.x
def pessimistic_requirement(arg)
arg.match(/(\d+(?:\.\d+)?)\.x/)
end
end
attr_accessor :name, :requirement, :source
private :name=, :requirement=, :source=
def initialize(name, requirement, source)
assert_name_valid! name
self.name = name
self.requirement = Requirement.new(requirement)
self.source = source
@manifests = nil
end
def manifests
@manifests ||= cache_manifests!
end
def cache_manifests!
source.manifests(name)
end
def satisfied_by?(manifest)
manifest.satisfies?(self)
end
def to_s
"#{name} (#{requirement}) <#{source}>"
end
def ==(other)
!other.nil? &&
self.class == other.class &&
self.name == other.name &&
self.requirement == other.requirement &&
self.source == other.source
end
alias :eql? :==
def hash
self.to_s.hash
end
def consistent_with?(other)
name != other.name || requirement.consistent_with?(other.requirement)
end
def inconsistent_with?(other)
!consistent_with?(other)
end
class << self
# merge dependencies with the same name into one
# with the source of the first one and merged requirements
def merge_dependencies(dependencies)
requirement = Dependency::Requirement.new(*dependencies.map{|d| d.requirement})
dependencies.last.class.new(dependencies.last.name, requirement, dependencies.last.source)
end
# Avoid duplicated dependencies with different sources or requirements
# Return [merged dependnecies, duplicates as a map by name]
def remove_duplicate_dependencies(dependencies)
uniq = []
duplicated = {}
dependencies_by_name = dependencies.group_by{|d| d.name}
dependencies_by_name.map do |name, dependencies_same_name|
if dependencies_same_name.size > 1
duplicated[name] = dependencies_same_name
uniq << merge_dependencies(dependencies_same_name)
else
uniq << dependencies_same_name.first
end
end
[uniq, duplicated]
end
end
private
def assert_name_valid!(name)
name =~ /\A\S(?:.*\S)?\z/ and return
raise ArgumentError, "name (#{name.inspect}) must be sensible"
end
end
end
|