blob: fd7f6cd1ffefbc0df68dd90df96828385cd404b7 [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with this
# work for additional information regarding copyright ownership. The ASF
# licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
module Buildr #:nodoc:
#
# See ArtifactNamespace#need
class VersionRequirement
CMP_PROCS = Gem::Requirement::OPS.dup
CMP_REGEX = Gem::Requirement::OP_RE.dup
CMP_CHARS = CMP_PROCS.keys.join
BOOL_CHARS = '\|\&\!'
VER_CHARS = '\w\.\-'
class << self
# is +str+ a version string?
def version?(str)
/^\s*[#{VER_CHARS}]+\s*$/ === str
end
# is +str+ a version requirement?
def requirement?(str)
/[#{BOOL_CHARS}#{CMP_CHARS}\(\)]/ === str
end
# :call-seq:
# VersionRequirement.create(" >1 <2 !(1.5) ") -> requirement
#
# parse the +str+ requirement
def create(str)
instance_eval normalize(str)
rescue StandardError => e
raise "Failed to parse #{str.inspect} due to: #{e}"
end
private
def requirement(req)
unless req =~ /^\s*(#{CMP_REGEX})?\s*([#{VER_CHARS}]+)\s*$/
raise "Invalid requirement string: #{req}"
end
comparator, version = $1, $2
# dup required due to jruby 1.7.13 bug/feature that caches versions?
version = Gem::Version.new(0).dup.tap { |v| v.version = version }
VersionRequirement.new(nil, [$1, version])
end
def negate(vreq)
vreq.negative = !vreq.negative
vreq
end
def normalize(str)
str = str.strip
if str[/[^\s\(\)#{BOOL_CHARS + VER_CHARS + CMP_CHARS}]/]
raise "version string #{str.inspect} contains invalid characters"
end
str.gsub!(/\s+(and|\&\&)\s+/, ' & ')
str.gsub!(/\s+(or|\|\|)\s+/, ' | ')
str.gsub!(/(^|\s*)not\s+/, ' ! ')
pattern = /(#{CMP_REGEX})?\s*[#{VER_CHARS}]+/
left_pattern = /[#{VER_CHARS}\)]$/
right_pattern = /^(#{pattern}|\()/
str = str.split.inject([]) do |ary, i|
ary << '&' if ary.last =~ left_pattern && i =~ right_pattern
ary << i
end
str = str.join(' ')
str.gsub!(/!([^=])?/, ' negate \1')
str.gsub!(pattern) do |expr|
case expr.strip
when 'not', 'negate' then 'negate '
else 'requirement("' + expr + '")'
end
end
str.gsub!(/negate\s+\(/, 'negate(')
str
end
end
def initialize(op, *requirements) #:nodoc:
@op, @requirements = op, requirements
end
# Is this object a composed requirement?
# VersionRequirement.create('1').composed? -> false
# VersionRequirement.create('1 | 2').composed? -> true
# VersionRequirement.create('1 & 2').composed? -> true
def composed?
requirements.size > 1
end
# Return the last requirement on this object having an = operator.
def default
default = nil
requirements.reverse.find do |r|
if Array === r
if !negative && (r.first.nil? || r.first.include?('='))
default = r.last.to_s
end
else
default = r.default
end
end
default
end
# Test if this requirement can be satisfied by +version+
def satisfied_by?(version)
return false unless version
unless version.kind_of?(Gem::Version)
raise "Invalid version: #{version.inspect}" unless self.class.version?(version)
# dup required due to jruby 1.7.13 bug/feature that caches versions?
version = Gem::Version.new(0).dup.tap { |v| v.version = version.strip }
end
message = op == :| ? :any? : :all?
result = requirements.send message do |req|
if Array === req
cmp, rv = *req
CMP_PROCS[cmp || '='].call(version, rv)
else
req.satisfied_by?(version)
end
end
negative ? !result : result
end
# Either modify the current requirement (if it's already an or operation)
# or create a new requirement
def |(other)
operation(:|, other)
end
# Either modify the current requirement (if it's already an and operation)
# or create a new requirement
def &(other)
operation(:&, other)
end
# return the parsed expression
def to_s
str = requirements.map(&:to_s).join(" " + @op.to_s + " ").to_s
str = "( " + str + " )" if negative || requirements.size > 1
str = "!" + str if negative
str
end
attr_accessor :negative
protected
attr_reader :requirements, :op
def operation(op, other)
@op ||= op
if negative == other.negative && @op == op && other.requirements.size == 1
@requirements << other.requirements.first
self
else
self.class.new(op, self, other)
end
end
end # VersionRequirement
end