| # 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. |
| |
| |
| # Rubygems 1.3.6 removed the 'version' accessor so monkey-patch it back to |
| # circumvent version validation. This is needed because Gem::Version doesn't |
| # accept version specs with dashes. |
| unless Gem::Version.new("0").respond_to?(:version=) |
| class Gem::Version |
| def version=(version) |
| @version = version.to_s.strip |
| |
| # re-prime @segments |
| @segments = nil |
| segments |
| end |
| end |
| end |
| |
| module Buildr #:nodoc: |
| |
| # |
| # See ArtifactNamespace#need |
| class VersionRequirement |
| |
| CMP_PROCS = Gem::Requirement::OPS.dup |
| CMP_REGEX = if defined?(Gem::Requirement::OP_RE) |
| Gem::Requirement::OP_RE |
| else |
| Gem::Requirement::OPS.keys.map { |k| Regexp.quote k }.join "|" |
| end |
| CMP_CHARS = CMP_PROCS.keys.join |
| BOOL_CHARS = '\|\&\!' |
| VER_CHARS = '\w\.\-' |
| |
| class << self |
| # is +str+ a version string? |
| def version?(str) |
| /^\s*r?\d[#{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 |