# ----------------------------------------------------------------------------- # # Versionomy standard format implementation # # ----------------------------------------------------------------------------- # Copyright 2008-2009 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 module Format # Get the standard format. # This is identical to calling get('standard'). # # The standard format is designed to handle most commonly-used version # number forms, and allow parsing and comparison between them. # # The standard schema is the heart of this format, providing a # common structure for most version numbers. # # 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: # :development, :alpha, :beta, # :preview, :release_candidate, :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 number fields 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 format itself is a delimiter-based format that understands a # wide variety of string representations of this version schema. # Examples of supported syntax include: # # 2.0 # 2.0.42.10 # 2.0b2 # 2.0rc15 # 2.0-5 # 2.0p5 # 2.0 Alpha 1 # 2.0a5.3 # 2.1.42.10-4.3 # # Because the standard format is based on Versionomy::Format::Delimiter, # a number of parameters are available for parsing and unparsing. See # the documentation for the delimiter class for more information. # # Two of the fields have styles that can be set when unparsing. # The :release_type field can be unparsed as either # :long style (e.g. "1.0alpha2") or :short style # (e.g. "1.0a2"). The patchlevel field can be unparsed as either # :number style (e.g. "2.1-1") or :letter style # (e.g. "2.1a"). Most fields can have their delimiter specified during # unparsing as well. # # For the exact annotated definition of the standard schema and format, # see the source code for Versionomy::Format::Standard#create. def self.standard get('standard') end # This is a namespace for the implementation of the Standard schema # and format. module Standard # Extra methods added to version values that use the standard schema. module ExtraMethods # Returns true if the version is a prerelease version def prerelease? self.release_type != :final end # Returns the release for this version. # For example, converts "1.2.0a1" to "1.2.0". # Non-prerelease versions return themselves. def release self.change(:release_type => :final) end end # Create the standard format. # This method is called internally when Versionomy initializes itself, # and you should not need to call it again. It is documented, however, # so that you can inspect its source code from RDoc, since the source # contains useful examples of how to use the schema and format # definition DSLs. def self.create # The following is the definition of the standard schema schema_ = Schema.create do # The major field has the default value of 1. Most other fields # have a default value of 0. Thus, the default version number # overall is "1.0". # We first create the core version fields "major.minor.tiny.tiny2". field(:major, :default_value => 1) do field(:minor) do field(:tiny) do field(:tiny2) do # The next field is a symbolic field that specifies the # release type: e.g. beta, release candidate, release, etc. field(:release_type, :type => :symbol) do # Development releases are typically expressed like # "1.0d3" and are intended for unstable development # progress. Bumping the release type will change it to # alpha. symbol(:development, :bump => :alpha) # Alpha releases are typically expressed like "1.0a2" and # are intended for internal testing. # Bumping the release type advances to beta. symbol(:alpha, :bump => :beta) # Beta releases are typically expressed like "1.0b2" and # are intended for external or public testing. # Bumping the release type advances to release candidate. symbol(:beta, :bump => :release_candidate) # Release candidate releases are typically expressed like # "1.0rc2" and are intended for final public testing # prior to release. # Bumping the release type advances to final release. symbol(:release_candidate, :bump => :final) # Preview releases represent an alternative release type # progression, and are typically used for public testing # similar to beta or release candidate. # Bumping the release type advances to final release. symbol(:preview, :bump => :final) # This type represents a final release. This is the # default value for the release_type field if no value is # explicitly provided. # Bumping the release type has no effect. symbol(:final, :bump => :final) default_value(:final) # If the release type is development, these fields are # made available to indicate which development release # is being represented. field(:development_version, :only => :development, :default_value => 1) do field(:development_minor) end # If the release type is alpha, these fields are made # available to indicate which alpha release is being # represented. field(:alpha_version, :only => :alpha, :default_value => 1) do field(:alpha_minor) end # If the release type is beta, these fields are made # available to indicate which beta release is being # represented. field(:beta_version, :only => :beta, :default_value => 1) do field(:beta_minor) end # If the release type is release candidate, these fields # are made available to indicate which release candidate # is being represented. field(:release_candidate_version, :only => :release_candidate, :default_value => 1) do field(:release_candidate_minor) end # If the release type is preview, these fields are made # available to indicate which preview release is being # represented. field(:preview_version, :only => :preview, :default_value => 1) do field(:preview_minor) end # If the release type is final, these fields are made # available to indicate an optional patchlevel. field(:patchlevel, :only => :final) do field(:patchlevel_minor) end end end end end end # Add the methods in this module to each value add_module(Format::Standard::ExtraMethods) end # The following is the definition of the standard format. It # understands the standard schema defined above. format_ = Format::Delimiter.new(schema_) do # All version number strings must start with the major version. # Unlike other fields, it is not preceded by the usual "dot" # delimiter, but it can be preceded by a "v" indicator. field(:major) do recognize_number(:delimiter_regexp => '(v\s?)?', :default_delimiter => '') end # The remainder of the core version number are represented as # integers delimited by periods by default. Each is also dependent # on the presence of the previous field, so :requires_previous_field # retains its default value of true. Finally, they can be optional # in an unparsed string if they are set to the default value of 0. field(:minor) do recognize_number(:default_value_optional => true) end field(:tiny) do recognize_number(:default_value_optional => true) end field(:tiny2) do recognize_number(:default_value_optional => true) end # The release type field is the most complex field because of the # variety of syntaxes we support. The basic strategy is to map # a few specific sets of characters as signaling particular release # types. For example, the "a" in "1.0a5" signals an alpha release. # If no such release type marker is found, it defaults to the final # release type. # We set up two styles, a short style and a long style. Short style # syntax looks like "1.0a5". Long syntax looks more like # "1.0 Alpha 5". The parsed value retains knowledge of which style # it came from so it can be reconstructed when the value is unparsed. # Note that we turn requires_previous_field off because the release # type syntax markers don't require any particular set of the core # version number fields to be present. "1.0a5" and "1.0.0.0a5" are # both valid version numbers. field(:release_type, :requires_previous_field => false, :default_style => :short) do # Some markers require a prerelease version (e.g. the 5 in # "1.0a5") while others don't (e.g. "1.9.2dev"). This is because # the syntax "1.0a" looks like a patchlevel syntax. So some of # the following recognizers set requires_next_field while others # do not. # Also note that we omit the value :final. This is # because that value is signaled by the absence of any syntax in # the version string, including the absence of any delimiters. # So we just allow it to fall through to the default. recognize_regexp_map(:style => :long, :default_delimiter => '', :delimiter_regexp => '-|\.|\s?') do map(:development, 'dev') map(:alpha, 'alpha') map(:beta, 'beta') map(:preview, 'preview') end recognize_regexp_map(:style => :short, :default_delimiter => '', :delimiter_regexp => '-|\.|\s?') do map(:release_candidate, 'rc') map(:preview, 'pre') end recognize_regexp_map(:style => :long, :default_delimiter => '', :delimiter_regexp => '-|\.|\s?') do map(:release_candidate, 'rc') end recognize_regexp_map(:style => :short, :default_delimiter => '', :delimiter_regexp => '-|\.|\s?', :requires_next_field => true) do map(:development, 'd') map(:alpha, 'a') map(:beta, 'b') end end # The main prerelease version may sometimes be optional, so we # mark it as optional here. If it is required, that will be # signalled by requires_next_field on the release_type field. # Minor prerelease versions are always optional. # Note that we override the default_value (normally 1) and set # it to 0 if a main prerelease version is not present. This is # so schema-oriented operations like bumping will set the value # to 1, while parsing a string will yield 0 when the field is # missing (e.g. we want "1.9.2dev" to mean "1.9.2dev0".) field(:development_version, :default_value => 0) do recognize_number(:delimiter_regexp => '-|\.|\s?', :default_delimiter => '', :default_value_optional => true) end field(:development_minor) do recognize_number(:default_value_optional => true) end field(:alpha_version, :default_value => 0) do recognize_number(:delimiter_regexp => '-|\.|\s?', :default_delimiter => '', :default_value_optional => true) end field(:alpha_minor) do recognize_number(:default_value_optional => true) end field(:beta_version, :default_value => 0) do recognize_number(:delimiter_regexp => '-|\.|\s?', :default_delimiter => '', :default_value_optional => true) end field(:beta_minor) do recognize_number(:default_value_optional => true) end field(:release_candidate_version, :default_value => 0) do recognize_number(:delimiter_regexp => '-|\.|\s?', :default_delimiter => '', :default_value_optional => true) end field(:release_candidate_minor) do recognize_number(:default_value_optional => true) end field(:preview_version, :default_value => 0) do recognize_number(:delimiter_regexp => '-|\.|\s?', :default_delimiter => '', :default_value_optional => true) end field(:preview_minor) do recognize_number(:default_value_optional => true) end # The patchlevel field does not require the previous field (which is # release_type). Here we also set up two styles: a numeric style and # a letter style. So "1.0a" and "1.0-1" are equivalent. field(:patchlevel, :requires_previous_field => false, :default_value_optional => true, :default_style => :number) do recognize_number(:style => :number, :default_delimiter => '-', :delimiter_regexp => '(-|\.|\s?)(p|u)|-|_') recognize_letter(:style => :letter, :default_delimiter => '', :delimiter_regexp => '-|\.|\s?', :expected_follower_regexp => '\z') end field(:patchlevel_minor) do recognize_number(:default_value_optional => true) end # By default, we require that at least the major and minor fields # appear in an unparsed version string. default_unparse_params(:required_fields => [:minor, :development_version, :alpha_version, :beta_version, :release_candidate_version, :preview_version]) end end end register('standard', Format::Standard.create) unless get('standard') end end