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)
signature.asc
Description: OpenPGP digital signature