On 09/18/2015 04:32 AM, konsolebox wrote:
> 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.
> 
> 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
> 
Are you stating this is for package epochs?

-- 
-- Matthew Thode (prometheanfire)

Attachment: signature.asc
Description: OpenPGP digital signature

Reply via email to