Module: Vers::PyPIVersion

Extended by:
PyPIVersion
Included in:
PyPIVersion
Defined in:
lib/vers/pypi_version.rb

Defined Under Namespace

Classes: LocalPart, Parsed

Constant Summary collapse

PATTERN =
/\A\s*v?
(?:(\d+)!)?
(\d+(?:\.\d+)*)
(?:[-_.]?(alpha|beta|preview|pre|rc|a|b|c)[-_.]?(\d*))?
(?:(?:[-_.]?(post|rev|r)[-_.]?(\d*))|(?:-(\d+)))?
(?:[-_.]?(dev)[-_.]?(\d*))?
(?:\+([a-z0-9]+(?:[-_.][a-z0-9]+)*))?
\s*\z/ix
PRE_TAGS =
{
  "a" => 0,
  "alpha" => 0,
  "b" => 1,
  "beta" => 1,
  "c" => 2,
  "rc" => 2,
  "pre" => 2,
  "preview" => 2
}.freeze

Instance Method Summary collapse

Instance Method Details

#compare(left, right) ⇒ Object



40
41
42
43
44
45
46
47
48
# File 'lib/vers/pypi_version.rb', line 40

def compare(left, right)
  parsed_left = parse(left)
  parsed_right = parse(right)
  comparison = compare_public_parsed(left, right, parsed_left, parsed_right)
  return comparison unless comparison.zero?
  return comparison unless parsed_left && parsed_right

  compare_local(parsed_left.local, parsed_right.local)
end

#compare_dev(left, right) ⇒ Object



222
223
224
225
226
227
228
# File 'lib/vers/pypi_version.rb', line 222

def compare_dev(left, right)
  return -1 if !left.dev_number.nil? && right.dev_number.nil?
  return 1 if left.dev_number.nil? && !right.dev_number.nil?
  return 0 if left.dev_number.nil?

  VersionComparison.compare_numbers(left.dev_number, right.dev_number)
end

#compare_local(left, right) ⇒ Object



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/vers/pypi_version.rb', line 230

def compare_local(left, right)
  return 0 if left.empty? && right.empty?
  return -1 if left.empty?
  return 1 if right.empty?

  [left.length, right.length].max.times do |index|
    return -1 unless left[index]
    return 1 unless right[index]

    left_part = left[index]
    right_part = right[index]
    if left_part.numeric != right_part.numeric
      return left_part.numeric ? 1 : -1
    end

    comparison = if left_part.numeric
                   VersionComparison.compare_numbers(left_part.value, right_part.value)
                 else
                   left_part.value <=> right_part.value
                 end
    return comparison unless comparison.zero?
  end

  0
end

#compare_post(left, right) ⇒ Object



214
215
216
217
218
219
220
# File 'lib/vers/pypi_version.rb', line 214

def compare_post(left, right)
  return 1 if !left.post_number.nil? && right.post_number.nil?
  return -1 if left.post_number.nil? && !right.post_number.nil?
  return 0 if left.post_number.nil?

  VersionComparison.compare_numbers(left.post_number, right.post_number)
end

#compare_pre(left, right) ⇒ Object



194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/vers/pypi_version.rb', line 194

def compare_pre(left, right)
  left_rank = pre_rank(left)
  right_rank = pre_rank(right)
  comparison = left_rank <=> right_rank
  return comparison unless comparison.zero?
  return 0 if left.pre_tag.nil?

  comparison = left.pre_tag <=> right.pre_tag
  return comparison unless comparison.zero?

  VersionComparison.compare_numbers(left.pre_number, right.pre_number)
end

#compare_public(left, right) ⇒ Object



50
51
52
53
54
# File 'lib/vers/pypi_version.rb', line 50

def compare_public(left, right)
  parsed_left = parse(left)
  parsed_right = parse(right)
  compare_public_parsed(left, right, parsed_left, parsed_right)
end

#compare_public_parsed(left, right, parsed_left, parsed_right) ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/vers/pypi_version.rb', line 56

def compare_public_parsed(left, right, parsed_left, parsed_right)
  return Version.compare(left, right) unless parsed_left && parsed_right

  comparison = VersionComparison.compare_numbers(parsed_left.epoch, parsed_right.epoch)
  return comparison unless comparison.zero?

  comparison = compare_release(parsed_left.release, parsed_right.release)
  return comparison unless comparison.zero?

  comparison = compare_pre(parsed_left, parsed_right)
  return comparison unless comparison.zero?

  comparison = compare_post(parsed_left, parsed_right)
  return comparison unless comparison.zero?

  comparison = compare_dev(parsed_left, parsed_right)
  return comparison unless comparison.zero?

  0
end

#compare_release(left, right) ⇒ Object



185
186
187
188
189
190
191
192
# File 'lib/vers/pypi_version.rb', line 185

def compare_release(left, right)
  [left.length, right.length].max.times do |index|
    comparison = VersionComparison.compare_numbers(left[index], right[index])
    return comparison unless comparison.zero?
  end

  0
end

#normalize(value) ⇒ Object

Raises:

  • (ArgumentError)


116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/vers/pypi_version.rb', line 116

def normalize(value)
  parsed = parse(value)
  raise ArgumentError, "invalid pypi version: #{value}" unless parsed

  result = +""
  result << "#{VersionComparison.normalize_number(parsed.epoch)}!" unless VersionComparison.compare_numbers(parsed.epoch, "0").zero?
  result << parsed.release.map { |part| VersionComparison.normalize_number(part) }.join(".")

  unless parsed.pre_tag.nil?
    result << %w[a b rc].fetch(parsed.pre_tag)
    result << VersionComparison.normalize_number(parsed.pre_number)
  end
  result << ".post#{VersionComparison.normalize_number(parsed.post_number)}" unless parsed.post_number.nil?
  result << ".dev#{VersionComparison.normalize_number(parsed.dev_number)}" unless parsed.dev_number.nil?

  if parsed.local.any?
    result << "+"
    result << parsed.local.map { |part| part.numeric ? VersionComparison.normalize_number(part.value) : part.value }.join(".")
  end

  result
end

#parse(value) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/vers/pypi_version.rb', line 77

def parse(value)
  match = PATTERN.match(value.to_s)
  return nil unless match

  pre_tag = match[3]&.downcase
  post_number = match[7] || (match[5] ? match[6] : nil)
  local = if match[10]
            match[10].downcase.split(/[._-]/).map do |part|
              LocalPart.new(part, VersionComparison.numeric?(part))
            end
          else
            []
          end

  Parsed.new(
    match[1].to_s,
    match[2].split("."),
    pre_tag && PRE_TAGS.fetch(pre_tag),
    pre_tag ? match[4].to_s : nil,
    post_number,
    match[8] ? match[9].to_s : nil,
    local.freeze
  )
end

#pre_rank(version) ⇒ Object



207
208
209
210
211
212
# File 'lib/vers/pypi_version.rb', line 207

def pre_rank(version)
  return -1 if version.pre_tag.nil? && version.post_number.nil? && !version.dev_number.nil?
  return 1 if version.pre_tag.nil?

  0
end

#prerelease?(value) ⇒ Boolean

Returns:

  • (Boolean)


106
107
108
109
# File 'lib/vers/pypi_version.rb', line 106

def prerelease?(value)
  parsed = parse(value)
  parsed && (!parsed.pre_tag.nil? || !parsed.dev_number.nil?)
end

#same_release?(left, right) ⇒ Boolean

Returns:

  • (Boolean)


156
157
158
159
# File 'lib/vers/pypi_version.rb', line 156

def same_release?(left, right)
  VersionComparison.compare_numbers(left.epoch, right.epoch).zero? &&
    compare_release(left.release, right.release).zero?
end

#specifier_equal?(version, specifier) ⇒ Boolean

Returns:

  • (Boolean)


139
140
141
142
143
144
# File 'lib/vers/pypi_version.rb', line 139

def specifier_equal?(version, specifier)
  parsed_version = parse(version)
  parsed_specifier = parse(specifier)
  return compare(version, specifier).zero? unless parsed_version && parsed_specifier
  versions_equal?(parsed_version, parsed_specifier, ignore_local: parsed_specifier.local.empty?)
end

#stable?(value) ⇒ Boolean

Returns:

  • (Boolean)


111
112
113
114
# File 'lib/vers/pypi_version.rb', line 111

def stable?(value)
  parsed = parse(value)
  parsed && parsed.pre_tag.nil? && parsed.dev_number.nil?
end

#valid?(value) ⇒ Boolean

Returns:

  • (Boolean)


102
103
104
# File 'lib/vers/pypi_version.rb', line 102

def valid?(value)
  !parse(value).nil?
end

#versions_equal?(left, right, ignore_local: false) ⇒ Boolean

Returns:

  • (Boolean)


146
147
148
149
150
151
152
153
154
# File 'lib/vers/pypi_version.rb', line 146

def versions_equal?(left, right, ignore_local: false)
  local_equal = ignore_local || compare_local(left.local, right.local).zero?
  VersionComparison.compare_numbers(left.epoch, right.epoch).zero? &&
    compare_release(left.release, right.release).zero? &&
    compare_pre(left, right).zero? &&
    compare_post(left, right).zero? &&
    compare_dev(left, right).zero? &&
    local_equal
end

#without_dev(version) ⇒ Object



173
174
175
176
177
178
179
180
181
182
183
# File 'lib/vers/pypi_version.rb', line 173

def without_dev(version)
  Parsed.new(
    version.epoch,
    version.release,
    version.pre_tag,
    version.pre_number,
    version.post_number,
    nil,
    [].freeze
  )
end

#without_post_and_dev(version) ⇒ Object



161
162
163
164
165
166
167
168
169
170
171
# File 'lib/vers/pypi_version.rb', line 161

def without_post_and_dev(version)
  Parsed.new(
    version.epoch,
    version.release,
    version.pre_tag,
    version.pre_number,
    nil,
    nil,
    [].freeze
  )
end