# -----------------------------------------------------------------------------
#
# Versionomy standard schema and formats
#
# -----------------------------------------------------------------------------
# Copyright 2008 Daniel Azuma
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the copyright holder, nor the names of any other
# contributors to this software, may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# -----------------------------------------------------------------------------
;
module Versionomy
# === The standard schema
#
# The standard schema is designed to handle most commonly-used version
# number forms, and allow parsing and comparison between them.
#
# It begins with four numeric fields: "major.minor.tiny.tiny2".
#
# The next field, "release_type", defines the remaining structure.
# The release type can be one of these symbolic values: :prerelease,
# :development, :alpha, :beta,
# :release_candidate, or :release.
#
# Depending on that value, additional fields become available. For example,
# the :alpha value enables the fields "alpha_version"
# and "alpha_minor", which represent version numbers after the "a" alpha
# specifier. i.e. "2.1a30" has an alpha_version of 30. "2.1a30.2" also
# has an alpha_minor of 2. Similarly, the :beta release_type
# value enables the fields "beta_version" and "beta_minor". A release_type
# of :release enables "patchlevel" and "patchlevel_minor", to
# support versions like "1.8.7p72".
#
# The full definition of the standard schema is as follows:
#
# Schema.new(:major, :initial => 1) do
# schema(:minor) do
# schema(:tiny) do
# schema(:tiny2) do
# schema(:release_type, :type => :symbol) do
# symbol(:prerelease, :bump => :release)
# symbol(:development, :bump => :alpha)
# symbol(:alpha, :bump => :beta)
# symbol(:beta, :bump => :release_candidate)
# symbol(:release_candidate, :bump => :release)
# symbol(:release, :bump => :release)
# initial_value(:release)
# schema(:prerelease_version, :only => :prerelease, :initial => 1) do
# schema(:prerelease_minor)
# end
# schema(:development_version, :only => :development, :initial => 1) do
# schema(:development_minor)
# end
# schema(:alpha_version, :only => :alpha, :initial => 1) do
# schema(:alpha_minor)
# end
# schema(:beta_version, :only => :beta, :initial => 1) do
# schema(:beta_minor)
# end
# schema(:release_candidate_version, :only => :release_candidate, :initial => 1) do
# schema(:release_candidate_minor)
# end
# schema(:patchlevel, :only => :release) do
# schema(:patchlevel_minor)
# end
# end
# end
# end
# end
# end
module Standard
# A formatter for the standard schema
class StandardFormat
# Create a new formatter
def initialize(opts_={})
@patchlevel_separator = opts_[:patchlevel_separators] || ['-', 'p']
@prerelease_symbol = opts_[:prerelease_symbols] || 'pre'
@development_symbol = opts_[:development_symbols] || 'd'
@alpha_symbol = opts_[:alpha_symbols] || 'a'
@beta_symbol = opts_[:beta_symbols] || 'b'
@release_candidate_symbol = opts_[:release_candidate_symbols] || 'rc'
end
def _create_regex(given_, default_) # :nodoc:
if given_
if given_.respond_to?(:join)
given_.join('|')
else
given_.to_s
end
else
if default_.respond_to?(:join)
default_.join('|')
else
default_.to_s
end
end
end
private :_create_regex
def _create_separator(given_, default_) # :nodoc:
if given_
given_.to_s
else
if default_.respond_to?(:join)
default_[0].to_s
else
default_.to_s
end
end
end
private :_create_separator
# Parse a string for the standard schema.
def parse(schema_, str_, params_)
params_ = Hash.new.merge(params_)
hash_ = Hash.new
if str_ =~ /^(\d+)(.*)$/
hash_[:major] = $1.to_i
str_ = $2
else
hash_[:major] = 0
end
if str_ =~ /^\.(\d+)(.*)$/
hash_[:minor] = $1.to_i
str_ = $2
if str_ =~ /^\.(\d+)(.*)$/
hash_[:tiny] = $1.to_i
str_ = $2
if str_ =~ /^\.(\d+)(.*)$/
hash_[:tiny2] = $1.to_i
str_ = $2
params_[:required_fields] = 4
else
hash_[:tiny2] = 0
params_[:required_fields] = 3
end
else
hash_[:tiny] = 0
params_[:required_fields] = 2
end
else
hash_[:minor] = 0
params_[:required_fields] = 1
end
if str_ =~ /^(#{_create_regex(params_[:prerelease_symbol], @prerelease_symbol)})(\d+)(.*)$/
params_[:prerelease_symbol] = $1
hash_[:release_type] = :prerelease
hash_[:prerelease_version] = $2.to_i
str_ = $3
if str_ =~ /^\.(\d+)/
hash_[:prerelease_minor] = $1.to_i
params_[:prerelease_required_fields] = 2
else
params_[:prerelease_required_fields] = 1
end
elsif str_ =~ /^(#{_create_regex(params_[:development_symbol], @development_symbol)})(\d+)(.*)$/
params_[:development_symbol] = $1
hash_[:release_type] = :development
hash_[:development_version] = $2.to_i
str_ = $3
if str_ =~ /^\.(\d+)/
hash_[:development_minor] = $1.to_i
params_[:development_required_fields] = 2
else
params_[:development_required_fields] = 1
end
elsif str_ =~ /^(#{_create_regex(params_[:alpha_symbol], @alpha_symbol)})(\d+)(.*)$/
params_[:alpha_symbol] = $1
hash_[:release_type] = :alpha
hash_[:alpha_version] = $2.to_i
str_ = $3
if str_ =~ /^\.(\d+)/
hash_[:alpha_minor] = $1.to_i
params_[:alpha_required_fields] = 2
else
params_[:alpha_required_fields] = 1
end
elsif str_ =~ /^(#{_create_regex(params_[:beta_symbol], @beta_symbol)})(\d+)(.*)$/
params_[:beta_symbol] = $1
hash_[:release_type] = :beta
hash_[:beta_version] = $2.to_i
str_ = $3
if str_ =~ /^\.(\d+)/
hash_[:beta_minor] = $1.to_i
params_[:beta_required_fields] = 2
else
params_[:beta_required_fields] = 1
end
elsif str_ =~ /^(#{_create_regex(params_[:release_candidate_symbol], @release_candidate_symbol)})(\d+)(.*)$/
params_[:release_candidate_symbol] = $1
hash_[:release_candidate_version] = $2.to_i
hash_[:release_type] = :release_candidate
str_ = $3
if str_ =~ /^\.(\d+)/
hash_[:release_candidate_minor] = $1.to_i
params_[:release_candidate_required_fields] = 2
else
params_[:release_candidate_required_fields] = 1
end
else
hash_[:release_type] = :release
if str_ =~ /^(#{_create_regex(params_[:patchlevel_separator], @patchlevel_separator)})(\d+)(.*)$/
params_[:patchlevel_separator] = $1
params_[:patchlevel_format] = :digit
hash_[:patchlevel] = $2.to_i
str_ = $3
if str_ =~ /^\.(\d+)/
hash_[:patchlevel_minor] = $1.to_i
params_[:patchlevel_required_fields] = 2
else
params_[:patchlevel_required_fields] = 1
end
elsif str_ =~ /^([a-z])/
char_ = $1
params_[:patchlevel_format] = :alpha_lower
params_[:patchlevel_required_fields] = 1
hash[:patchlevel] = (char_.bytes.next rescue char_[0]) - 96
elsif str_ =~ /^([A-Z])/
char_ = $1
params_[:patchlevel_format] = :alpha_upper
params_[:patchlevel_required_fields] = 1
hash[:patchlevel] = (char_.bytes.next rescue char_[0]) - 64
end
end
Versionomy::Value._new(schema_, hash_, params_)
end
# Unparse a value for the standard schema.
def unparse(schema_, value_, params_)
params_ = value_.parse_params.merge(params_)
str_ = "#{value_.major}.#{value_.minor}.#{value_.tiny}.#{value_.tiny2}"
(4 - (params_[:required_fields] || 2)).times{ str_.sub!(/\.0$/, '') }
case value_.release_type
when :prerelease
prerelease_required_fields_ = params_[:prerelease_required_fields] || 1
str_ << _create_separator(params_[:prerelease_symbol], @prerelease_symbol)
str_ << value_.prerelease_version.to_s
if value_.prerelease_minor > 0 || prerelease_required_fields_ > 1
str_ << ".#{value_.prerelease_minor}"
end
when :development
development_required_fields_ = params_[:development_required_fields] || 1
str_ << _create_separator(params_[:development_symbol], @development_symbol)
str_ << value_.development_version.to_s
if value_.development_minor > 0 || development_required_fields_ > 1
str_ << ".#{value_.development_minor}"
end
when :alpha
alpha_required_fields_ = params_[:alpha_required_fields] || 1
str_ << _create_separator(params_[:alpha_symbol], @alpha_symbol)
str_ << value_.alpha_version.to_s
if value_.alpha_minor > 0 || alpha_required_fields_ > 1
str_ << ".#{value_.alpha_minor}"
end
when :beta
beta_required_fields_ = params_[:beta_required_fields] || 1
str_ << _create_separator(params_[:beta_symbol], @beta_symbol)
str_ << value_.beta_version.to_s
if value_.beta_minor > 0 || beta_required_fields_ > 1
str_ << ".#{value_.beta_minor}"
end
when :release_candidate
release_candidate_required_fields_ = params_[:release_candidate_required_fields] || 1
str_ << _create_separator(params_[:release_candidate_symbol], @release_candidate_symbol)
str_ << value_.release_candidate_version.to_s
if value_.release_candidate_minor > 0 || release_candidate_required_fields_ > 1
str_ << ".#{value_.release_candidate_minor}"
end
else
patchlevel_required_fields_ = params_[:patchlevel_required_fields] || 0
if value_.patchlevel > 0 || patchlevel_required_fields_ > 0
if params_[:patchlevel_format] == :alpha_lower
str_.concat(96 + value_.patchlevel)
elsif params_[:patchlevel_format] == :alpha_upper
str_.concat(64 + value_.patchlevel)
else
str_ << _create_separator(params_[:patchlevel_separator], @patchlevel_separator)
str_ << value_.patchlevel.to_s
if value_.patchlevel_minor > 0 || patchlevel_required_fields_ > 1
str_ << ".#{value_.patchlevel_minor}"
end
end
end
end
str_
end
end
# Get the standard schema
def self.schema
@standard_schema ||= SchemaCreator._create_schema
end
module SchemaCreator # :nodoc:
def self._create_schema
Schema.new(:major, :initial => 1) do
schema(:minor) do
schema(:tiny) do
schema(:tiny2) do
schema(:release_type, :type => :symbol) do
symbol(:prerelease, :bump => :release)
symbol(:development, :bump => :alpha)
symbol(:alpha, :bump => :beta)
symbol(:beta, :bump => :release_candidate)
symbol(:release_candidate, :bump => :release)
symbol(:release, :bump => :release)
initial_value(:release)
schema(:prerelease_version, :only => :prerelease, :initial => 1) do
schema(:prerelease_minor)
end
schema(:development_version, :only => :development, :initial => 1) do
schema(:development_minor)
end
schema(:alpha_version, :only => :alpha, :initial => 1) do
schema(:alpha_minor)
end
schema(:beta_version, :only => :beta, :initial => 1) do
schema(:beta_minor)
end
schema(:release_candidate_version, :only => :release_candidate, :initial => 1) do
schema(:release_candidate_minor)
end
schema(:patchlevel, :only => :release) do
schema(:patchlevel_minor)
end
end
end
end
end
define_format(:default, StandardFormat.new)
end
end
end
end
end