File: debian.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 (175 lines) | stat: -rw-r--r-- 5,836 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
168
169
170
171
172
173
174
175
module Puppet::Util::Package::Version
  class Debian < Numeric
    include Comparable

    # Version string matching regexes
    REGEX_EPOCH   = '(?:([0-9]+):)?'
    # alphanumerics and the characters . + - ~ , starts with a digit, ~ only of debian_revision is present
    REGEX_UPSTREAM_VERSION = '([\.\+~0-9a-zA-Z-]+?)'
    #alphanumerics and the characters + . ~
    REGEX_DEBIAN_REVISION = '(?:-([\.\+~0-9a-zA-Z]*))?'

    REGEX_FULL    = REGEX_EPOCH + REGEX_UPSTREAM_VERSION + REGEX_DEBIAN_REVISION.freeze
    REGEX_FULL_RX = /\A#{REGEX_FULL}\Z/

    class ValidationFailure < ArgumentError; end

    def self.parse(ver)
      raise ValidationFailure, "Unable to parse '#{ver}' as a string" unless ver.is_a?(String)

      match, epoch, upstream_version, debian_revision = *ver.match(REGEX_FULL_RX)

      raise ValidationFailure, "Unable to parse '#{ver}' as a debian version identifier" unless match

      new(epoch.to_i, upstream_version, debian_revision).freeze
    end

    def to_s
      s = @upstream_version
      s = "#{@epoch}:#{s}" if @epoch != 0
      s = "#{s}-#{@debian_revision}" if @debian_revision
      s
    end
    alias inspect to_s

    def eql?(other)
      other.is_a?(self.class) &&
        @epoch.eql?(other.epoch) &&
        @upstream_version.eql?(other.upstream_version) &&
        @debian_revision.eql?(other.debian_revision)
    end
    alias == eql?

    def <=>(other)
      return nil unless other.is_a?(self.class)
      cmp = @epoch <=> other.epoch
      if cmp == 0
        cmp = compare_upstream_version(other)
        if cmp == 0
          cmp = compare_debian_revision(other)
        end
      end
      cmp
    end

    attr_reader :epoch, :upstream_version, :debian_revision

    private

    def initialize(epoch, upstream_version, debian_revision)
      @epoch            = epoch
      @upstream_version = upstream_version
      @debian_revision  = debian_revision
    end

    def compare_upstream_version(other)
      mine = @upstream_version
      yours = other.upstream_version
      compare_debian_versions(mine, yours)
    end

    def compare_debian_revision(other)
      mine = @debian_revision
      yours = other.debian_revision
      compare_debian_versions(mine, yours)
    end

    def compare_debian_versions(mine, yours)
      #   First the initial part of each string consisting entirely of non-digit characters is determined.
      # These two parts (one of which may be empty) are compared lexically. If a difference is found it is
      # returned. The lexical comparison is a comparison of ASCII values modified so that all the letters
      # sort earlier than all the non-letters and so that a tilde sorts before anything, even the end of a
      # part. For example, the following parts are in sorted order from earliest to latest: ~~, ~~a, ~, the
      # empty part, a.
      #
      #   Then the initial part of the remainder of each string which consists entirely of digit characters
      # is determined. The numerical values of these two parts are compared, and any difference found is
      # returned as the result of the comparison. For these purposes an empty string (which can only occur
      # at the end of one or both version strings being compared) counts as zero.
      #
      #   These two steps (comparing and removing initial non-digit strings and initial digit strings) are
      # repeated until a difference is found or both strings are exhausted.

      mine_index = 0
      yours_index = 0
      cmp = 0
      mine ||= ''
      yours ||= ''
      while mine_index < mine.length && yours_index < yours.length && cmp == 0
        #handle ~
        _mymatch, mytilde = *match_tildes(mine.slice(mine_index..-1))
        mytilde ||= ''

        _yoursmatch, yourstilde = *match_tildes(yours.slice(yours_index..-1))
        yourstilde ||= ''

        cmp = -1 * (mytilde.length <=> yourstilde.length)
        mine_index += mytilde.length
        yours_index += yourstilde.length

        if cmp == 0 # handle letters

          _mymatch, myletters = *match_letters(mine.slice(mine_index..-1))
          myletters ||= ''

          _yoursmatch, yoursletters = *match_letters(yours.slice(yours_index..-1))
          yoursletters ||= ''

          cmp = myletters <=> yoursletters
          mine_index += myletters.length
          yours_index += yoursletters.length

          if cmp == 0 # handle nonletters except tilde
            _mymatch, mynon_letters = *match_non_letters(mine.slice(mine_index..-1))
            mynon_letters ||= ''

            _yoursmatch, yoursnon_letters = *match_non_letters(yours.slice(yours_index..-1))
            yoursnon_letters ||= ''

            cmp = mynon_letters <=> yoursnon_letters
            mine_index += mynon_letters.length
            yours_index += yoursnon_letters.length

            if cmp == 0 # handle digits
              _mymatch, mydigits = *match_digits(mine.slice(mine_index..-1))
              mydigits ||= ''

              _yoursmatch, yoursdigits = *match_digits(yours.slice(yours_index..-1))
              yoursdigits ||= ''

              cmp = mydigits.to_i <=> yoursdigits.to_i
              mine_index += mydigits.length
              yours_index += yoursdigits.length
            end
          end
        end
      end
      if cmp == 0
        if mine_index < mine.length && match_tildes(mine[mine_index])
          cmp = -1
        elsif yours_index < yours.length && match_tildes(yours[yours_index])
          cmp = 1
        else
          cmp = mine.length <=> yours.length
        end
      end
      cmp
    end

    def match_digits(a)
      a.match(/^([0-9]+)/)
    end

    def match_non_letters(a)
      a.match(/^([\.\+-]+)/)
    end

    def match_tildes(a)
      a.match(/^(~+)/)
    end

    def match_letters(a)
      a.match(/^([A-Za-z]+)/)
    end
  end
end