File: pip.rb

package info (click to toggle)
puppet-agent 7.23.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 19,092 kB
  • sloc: ruby: 245,074; sh: 456; makefile: 38; xml: 33
file content (167 lines) | stat: -rw-r--r-- 5,721 bytes parent folder | download
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
module Puppet::Util::Package::Version
  class Pip
    include Comparable

    VERSION_PATTERN = "
      v?
      (?:
        (?:(?<epoch>[0-9]+)!)?                              # epoch
        (?<release>[0-9]+(?:\\.[0-9]+)*)                    # release segment
        (?<pre>                                             # pre-release
          [-_\\.]?
          (?<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
          [-_\\.]?
          (?<pre_n>[0-9]+)?
        )?
        (?<post>                                            # post release
          (?:-(?<post_n1>[0-9]+))
          |
          (?:
            [-_\\.]?
            (?<post_l>post|rev|r)
            [-_\\.]?
            (?<post_n2>[0-9]+)?
          )
        )?
        (?<dev>                                             # dev release
          [-_\\.]?
          (?<dev_l>dev)
          [-_\\.]?
          (?<dev_n>[0-9]+)?
        )?
      )
      (?:\\+(?<local>[a-z0-9]+(?:[-_\\.][a-z0-9]+)*))?      # local version
    ".freeze

    def self.parse(version)
      raise ValidationFailure, version.to_s unless version.is_a? String

      matched = version.match(Regexp.new(("^\\s*") + VERSION_PATTERN + ("\\s*$"), Regexp::EXTENDED | Regexp::MULTILINE | Regexp::IGNORECASE))
      raise ValidationFailure, version unless matched

      new(matched)
    end

    def self.compare(version_a, version_b)
      version_a = parse(version_a) unless version_a.is_a?(self)
      version_b = parse(version_b) unless version_b.is_a?(self)

      version_a <=> version_b
    end

    def to_s
      parts = []

      parts.push("#{@epoch_data}!")           if @epoch_data && @epoch_data != 0
      parts.push(@release_data.join("."))     if @release_data
      parts.push(@pre_data.join)              if @pre_data
      parts.push(".post#{@post_data[1]}")     if @post_data
      parts.push(".dev#{@dev_data[1]}")       if @dev_data
      parts.push("+#{@local_data.join(".")}") if @local_data

      parts.join
    end
    alias inspect to_s

    def eql?(other)
      other.is_a?(self.class) && key.eql?(other.key)
    end
    alias == eql?

    def <=>(other)
      raise ValidationFailure, other.to_s unless other.is_a?(self.class)
      compare(key, other.key)
    end

    attr_reader :key

    private

    def initialize(matched)
      @epoch_data   = matched[:epoch].to_i
      @release_data = matched[:release].split('.').map(&:to_i)                                       if matched[:release]
      @pre_data     = parse_letter_version(matched[:pre_l], matched[:pre_n])                         if matched[:pre_l]  || matched[:pre_n]
      @post_data    = parse_letter_version(matched[:post_l], matched[:post_n1] || matched[:post_n2]) if matched[:post_l] || matched[:post_n1] || matched[:post_n2]
      @dev_data     = parse_letter_version(matched[:dev_l], matched[:dev_n])                         if matched[:dev_l]  || matched[:dev_n]
      @local_data   = parse_local_version(matched[:local])                                           if matched[:local]

      @key = compose_key(@epoch_data, @release_data, @pre_data, @post_data, @dev_data, @local_data)
    end

    def parse_letter_version(letter, number)
      if letter
        number = 0 if !number
        letter.downcase!

        if letter == "alpha"
          letter = "a"
        elsif letter == "beta"
          letter = "b"
        elsif ["c", "pre", "preview"].include?(letter)
          letter = "rc"
        elsif ["rev", "r"].include?(letter)
          letter = "post"
        end

        return [letter, number.to_i]
      end

      ["post", number.to_i] if !letter && number
    end

    def parse_local_version(local_version)
      local_version.split(/[\\._-]/).map{|part| part =~ /[0-9]+/ && part !~ /[a-zA-Z]+/ ? part.to_i : part.downcase} if local_version
    end

    def compose_key(epoch, release, pre, post, dev, local)
      release_key = release.reverse
      release_key.each_with_index do |element, index|
        break unless element == 0
        release_key.delete_at(index) unless release_key.at(index + 1) != 0
      end
      release_key.reverse!

      if !pre && !post && dev
        pre_key = -Float::INFINITY
      else
        pre_key = pre || Float::INFINITY
      end

      post_key = post || -Float::INFINITY

      dev_key = dev || Float::INFINITY

      if !local
        local_key = [[-Float::INFINITY, ""]]
      else
        local_key = local.map{|i| (i.is_a? Integer) ? [i, ""] : [-Float::INFINITY, i]}
      end

      [epoch, release_key, pre_key, post_key, dev_key, local_key]
    end

    def compare(this, other)
      if (this.is_a? Array) && (other.is_a? Array)
        this  << -Float::INFINITY if this.length < other.length
        other << -Float::INFINITY if this.length > other.length

        this.each_with_index do |element, index|
          return compare(element, other.at(index)) if element != other.at(index)
        end
      elsif (this.is_a? Array) && !(other.is_a? Array)
        raise Puppet::Error, 'Cannot compare #{this} (Array) with #{other} (#{other.class}). Only ±Float::INFINITY accepted.' unless other.abs == Float::INFINITY
        return other == -Float::INFINITY ? 1 : -1
      elsif !(this.is_a? Array) && (other.is_a? Array)
        raise Puppet::Error, 'Cannot compare #{this} (#{this.class}) with #{other} (Array). Only ±Float::INFINITY accepted.' unless this.abs == Float::INFINITY
        return this == -Float::INFINITY ? -1 : 1
      end
      this <=> other
    end

    class ValidationFailure < ArgumentError
      def initialize(version)
        super("#{version} is not a valid python package version. Please refer to https://www.python.org/dev/peps/pep-0440/.")
      end
    end
  end
end