class Dependabot::Python::Requirement

Constants

OPS

Add equality and arbitrary-equality matchers

OR_SEPARATOR
PATTERN
PATTERN_RAW

Public Class Methods

new(*requirements) click to toggle source
Calls superclass method
# File lib/dependabot/python/requirement.rb, line 49
def initialize(*requirements)
  requirements = requirements.flatten.flat_map do |req_string|
    next if req_string.nil?

    req_string.split(",").map(&:strip).map do |r|
      convert_python_constraint_to_ruby_constraint(r)
    end
  end

  super(requirements)
end
parse(obj) click to toggle source
# File lib/dependabot/python/requirement.rb, line 24
def self.parse(obj)
  return ["=", Python::Version.new(obj.to_s)] if obj.is_a?(Gem::Version)

  unless (matches = PATTERN.match(obj.to_s))
    msg = "Illformed requirement [#{obj.inspect}]"
    raise BadRequirementError, msg
  end

  return DefaultRequirement if matches[1] == ">=" && matches[2] == "0"

  [matches[1] || "=", Python::Version.new(matches[2])]
end
requirements_array(requirement_string) click to toggle source

Returns an array of requirements. At least one requirement from the returned array must be satisfied for a version to be valid.

NOTE: Or requirements are only valid for Poetry.

# File lib/dependabot/python/requirement.rb, line 41
def self.requirements_array(requirement_string)
  return [new(nil)] if requirement_string.nil?

  requirement_string.strip.split(OR_SEPARATOR).map do |req_string|
    new(req_string.strip)
  end
end

Public Instance Methods

exact?() click to toggle source
# File lib/dependabot/python/requirement.rb, line 67
def exact?
  return false unless @requirements.size == 1

  %w(= == ===).include?(@requirements[0][0])
end
satisfied_by?(version) click to toggle source
# File lib/dependabot/python/requirement.rb, line 61
def satisfied_by?(version)
  version = Python::Version.new(version.to_s)

  requirements.all? { |op, rv| (OPS[op] || OPS["="]).call(version, rv) }
end

Private Instance Methods

convert_caret_req(req_string) click to toggle source

Poetry uses ^ requirements github.com/sdispater/poetry#caret-requirement

# File lib/dependabot/python/requirement.rb, line 100
def convert_caret_req(req_string)
  version = req_string.gsub(/^\^/, "")
  parts = version.split(".")
  parts.fill(0, parts.length...3)
  first_non_zero = parts.find { |d| d != "0" }
  first_non_zero_index =
    first_non_zero ? parts.index(first_non_zero) : parts.count - 1
  upper_bound = parts.map.with_index do |part, i|
    if i < first_non_zero_index then part
    elsif i == first_non_zero_index then (part.to_i + 1).to_s
    elsif i > first_non_zero_index && i == 2 then "0.a"
    else 0
    end
  end.join(".")

  [">= #{version}", "< #{upper_bound}"]
end
convert_python_constraint_to_ruby_constraint(req_string) click to toggle source
# File lib/dependabot/python/requirement.rb, line 75
def convert_python_constraint_to_ruby_constraint(req_string)
  return nil if req_string.nil?
  return nil if req_string == "*"

  req_string = req_string.gsub("~=", "~>")
  req_string = req_string.gsub(/(?<=\d)[<=>].*/, "")

  if req_string.match?(/~[^>]/) then convert_tilde_req(req_string)
  elsif req_string.start_with?("^") then convert_caret_req(req_string)
  elsif req_string.include?(".*") then convert_wildcard(req_string)
  else req_string
  end
end
convert_tilde_req(req_string) click to toggle source

Poetry uses ~ requirements. github.com/sdispater/poetry#tilde-requirements

# File lib/dependabot/python/requirement.rb, line 91
def convert_tilde_req(req_string)
  version = req_string.gsub(/^~\>?/, "")
  parts = version.split(".")
  parts << "0" if parts.count < 3
  "~> #{parts.join('.')}"
end
convert_wildcard(req_string) click to toggle source
# File lib/dependabot/python/requirement.rb, line 118
def convert_wildcard(req_string)
  # NOTE: This isn't perfect. It replaces the "!= 1.0.*" case with
  # "!= 1.0.0". There's no way to model this correctly in Ruby :'(
  quoted_ops = OPS.keys.sort_by(&:length).reverse.
               map { |k| Regexp.quote(k) }.join("|")
  op = req_string.match(/\A\s*(#{quoted_ops})?/).
       captures.first.to_s&.strip
  exact_op = ["", "=", "==", "==="].include?(op)

  req_string.strip.
    split(".").
    first(req_string.split(".").index { |s| s.include?("*") } + 1).
    join(".").
    gsub(/\*(?!$)/, "0").
    gsub(/\*$/, "0.a").
    tap { |s| exact_op ? s.gsub!(/^(?<!!)=*/, "~>") : s }
end