Dnia 18 września 2015 11:32:15 CEST, konsolebox <konsole...@gmail.com> 
napisał(a):
>This is what an ideal and simple versioning spec should look like to
>me.  (Not the form, but the concept).  I'm posting this here so it
>could be used as an added reference to anyone that would consider
>revising the current specification.

The length of this mail itself proves that this is far from simple. And 
similarly to the current solution it's full of silly special cases and magical 
rules. If you really want something simple and clean, take a look at the scheme 
used by pkg-config and rpm.

>
>Note: Assigning default values can be bypassed depending on the
>implementation.
>
>Comments are welcome.
>----------------------------------------
>1 Version Specification
>
>A version is composed of four component sets: the base part, the
>stage, the patch and the revision.
>
>Each component is composed mainly of version nodes to represent their
>level values.
>
>When a component is not specified, it gets a default node value of {0}.
>
>1.1 Version Nodes
>
>Nodes start basically with a number and then is optionally followed by
>a set of letters.
>
>Numbers and letters can coexist alternatingly to represent a single
>node.  The number of consecutive letters is also not limited to 1.
>For example: "1a4xy" is allowed (can be restricted).
>
>Each set of digits represents a single decimal number.  Leading zeros
>hold no meaning.
>
>The numerical equivalent of a set of letters is calculated in base of
>27 (0 exists along with it but is not included as a symbol).
>
>Version nodes after processing are basically just an array of signed
>integers where each set of digits or letters are converted to their
>numerical value.
>
>1.2 Version Parts
>
>1.2.1 The Base Part
>
>The base version is simply a set of version nodes that are separated
>by dots.  Examples: "4.1.2", "4.1.2aa" "4a", "4a.1", "4a.1.2".
>
>After processing, the base version is an array of version nodes.
>
>It is required.
>
>1.2.2 The Stage Part
>
>The stage part starts with _alpha, _beta, _pre or _rc.  Their
>numerical values are -4, -3, -2 or -1 respectively.  Each of them can
>optionally be followed by a version node string.  For example:
>"_alpha01".
>
>The resulting value for the stage after processing is a single version
>node where the first value in it is the numerical values of _alpha,
>_beta, _pre or _rc, and the other values are based on the added node
>string.
>
>A version without a stage has a default stage value of {0}.
>
>The stage part is optional and can be specified only once after the
>base part.  It can't be specified as a modifier for the patch, for the
>revision, or for another stage.
>
>1.2.3 The Patch Part
>
>The patch part begins with _p and is followed by a version node
>string.  For example: "_p20150105a".
>
>The patch part is optional and can be specified after the base part or
>after the stage part, but not after the revision.  It can only be
>specified once.
>
>It is processed as a single version node based on its version node
>string.
>
>A version without a patch has a default patch value of {0}.
>
>1.2.4 The Revision
>
>The revision starts with -r and is followed by a number.
>
>It is processed as a single version node based on its version node
>string.
>
>A version without a revision has a default revision value of {0}.
>
>1.3 Comparing Versions
>
>Versions are compared as version nodes and the algorithm is simple:
>each component and subcomponent is compared from left to right.
>
>Anything that gives a difference decides which version is greater or
>lesser.
>
>Any non-existing version-node has a default value of {0}.
>
>Any non-existing element of a version node has a default value of 0.
>
>If no difference is found during the process, it would mean that both
>versions are equal.
>
>1.3.1 Concept Code
>
>  #!/usr/bin/ruby
>
>  class ::Array
>    def adaptive_transpose
>      h_size = self.max_by{ |a| a.size }.size
>      v_size = self.size
>
>      result = Array.new(h_size)
>      with_index = self.each_with_index.to_a.freeze
>
>      0.upto(h_size - 1) do |i|
>        result[i] = Array.new(v_size)
>        with_index.each{ |a, j| result[i][j] = a[i] }
>      end
>
>      result
>    end
>  end
>
>  module Portage
>    class PackageVersion
>      class Node < ::Array
>        def initialize(*values)
>          self.concat(values)
>        end
>
>        def compare_with(another)
>          [self, another].adaptive_transpose.each do |a, b|
>            a ||= 0
>            b ||= 0
>            return -1 if a < b
>            return 1 if a > b
>          end
>
>          return 0
>        end
>
>        def self.parse(*args)
>          result = new
>
>          args.each do |a|
>            case a
>            when Integer
>              result << a
>            when /^[[:digit:]][[:alnum:]]*$/
>      a.scan(/[[:digit:]]+|[[:alpha:]]+/).each_with_index.map do |b, i|
>                if i.even?
>                  str = b.gsub(/^0+/, '')
>                  result << (str.empty? ? 0 : Integer(str))
>                else
>                  value = 0
>
>                  b.downcase.bytes.reverse.each_with_index do |c, i|
>                    value += 27 ** i * (c - 96)  ## a == 1, z == 26,
>and 0 exists but is not used
>                  end
>
>                  result << value
>                end
>              end
>            else
>           raise ArgumentError.new("Invalid node string: #{a.inspect}")
>            end
>          end
>
>          result
>        end
>
>        def self.zero
>          @zero ||= new(0)
>        end
>
>        private_class_method :new, :allocate
>      end
>
>      attr_accessor :base, :stage, :patch, :revision
>
>      def initialize(base, stage, patch, revision)
>        @base, @stage, @patch, @revision = base, stage, patch, revision
>      end
>
>      def compare_with(another)
>        [self.base, another.base].adaptive_transpose.each do |a, b|
>          a ||= Node.zero
>          b ||= Node.zero
>          r = a.compare_with(b)
>          return r unless r == 0
>        end
>
>        r = self.stage.compare_with(another.stage)
>        return r unless r == 0
>
>        r = self.patch.compare_with(another.patch)
>        return r unless r == 0
>
>        r = self.revision.compare_with(another.revision)
>        return r unless r == 0
>
>        return 0
>      end
>
>      STAGES = { 'alpha' => -4, 'beta' => -3, 'pre' => -2, 'rc' => -1 }
>REGEX =
>/^([[:digit:]][[:alnum:]]*(?:[.][[:alnum:]]+)*)?(?:_(alpha|beta|pre|rc)([[:digit:]][[:alnum:]]*)?)?(?:_p([[:digit:]][[:alnum:]]*))?(?:-r([[:digit:]]+))?(.+)?$/m
>
>      def self.parse(version_string)
>        __, base, stage, stage_ver, patch, revision, extra =
>version_string.match(REGEX).to_a
>        raise_invalid_version_string(version_string) if extra
>
>        begin
>          base = base.split('.').map{ |e| Node.parse(e) }
>          stage = stage ? stage_ver ? Node.parse(STAGES[stage],
>stage_ver) : Node.parse(STAGES[stage]) : Node.zero
>          patch = patch ? Node.parse(patch) : Node.zero
>          revision = revision ? Node.parse(revision) : Node.zero
>        rescue ArgumentError => e
>          raise_invalid_version_string("#{version_string}: #{e}")
>        end
>
>        new(base, stage, patch, revision)
>      end
>
>      def self.raise_invalid_version_string(version_string)
>   raise ArgumentError.new("Invalid version string: #{version_string}")
>      end
>
>    private_class_method :new, :allocate, :raise_invalid_version_string
>    end
>  end
>
>  samples = [
>    ["0", "0.01"],
>    ["0.01", "0.010"],
>    ["0.09", "0.090"],
>    ["0.10", "0.100"],
>    ["0.99", "0.990"],
>    ["0.100", "0.1000"],
>    ["0.100", "0.100"],
>    ["0.1", "0.1.1"],
>    ["0.1.1", "0.1a"],
>    ["0.1a", "0.2"],
>    ["0.2", "1"],
>    ["1", "1.0"],
>    ["1.0", "1.0_alpha"],
>    ["1.0_alpha", "1.0_alpha01"],
>    ["1.0_alpha01", "1.0_alpha01-r1"],
>    ["1.0_alpha01-r1", "1.0_alpha01_p20150105"],
>    ["1.0_alpha01_p20150105", "1.0_alpha01_p20150105-r1"],
>    ["1.0_alpha01", "1.0_beta"],
>    ["1.0_beta", "1.0_beta01"],
>    ["1.0_beta01", "1.0_pre01"],
>    ["1.0_pre01", "1.0_rc01"],
>    ["1.0_rc01", "1.0"],
>    ["1.0", "1.0-r1"],
>    ["1.0-r1", "1.0_p20150105"],
>    ["1.0_p20150105", "1.0_p20150105-r1"]
>  ]
>
>  samples.each do |a, b|
>    x = Portage::PackageVersion.parse(a)
>    y = Portage::PackageVersion.parse(b)
>    r = x.compare_with(y)
>    r = r < 0 ? '<' : r > 0 ? '>' : '=='
>    puts "#{a}    #{r}    #{b}"
>  end
>
>1.3.2 Concept Code Output
>
>  0    <    0.01
>  0.01    <    0.010
>  0.09    <    0.090
>  0.10    <    0.100
>  0.99    <    0.990
>  0.100    <    0.1000
>  0.100    ==    0.100
>  0.1    <    0.1.1
>  0.1.1    <    0.1a
>  0.1a    <    0.2
>  0.2    <    1
>  1    ==    1.0
>  1.0    >    1.0_alpha
>  1.0_alpha    <    1.0_alpha01
>  1.0_alpha01    <    1.0_alpha01-r1
>  1.0_alpha01-r1    <    1.0_alpha01_p20150105
>  1.0_alpha01_p20150105    <    1.0_alpha01_p20150105-r1
>  1.0_alpha01    <    1.0_beta
>  1.0_beta    <    1.0_beta01
>  1.0_beta01    <    1.0_pre01
>  1.0_pre01    <    1.0_rc01
>  1.0_rc01    <    1.0
>  1.0    <    1.0-r1
>  1.0-r1    <    1.0_p20150105
>  1.0_p20150105    <    1.0_p20150105-r1

-- 
Best regards,
Michał Górny

Reply via email to