Skip to content

Commit

Permalink
Documentation updates
Browse files Browse the repository at this point in the history
  • Loading branch information
dazuma committed Nov 5, 2009
1 parent 175d88d commit 014f6c9
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 61 deletions.
93 changes: 59 additions & 34 deletions README.rdoc
Expand Up @@ -8,44 +8,52 @@ numbers in the wide variety of versioning schemes in use.

Let's be honest. Version numbers are not easy to deal with, and very
seldom seem to be done right.
Imagine the common case of testing the ruby version. Most of us, if we
Imagine the common case of testing the Ruby version. Most of us, if we
need to worry about Ruby VM compatibility, will do something like:

do_something if RUBY_VERSION >= "1.8.7"

Treating the version number as a string is all well and good, until it
isn't. The above code works for Ruby 1.8.6, 1.8.7, 1.8.8, and 1.9.1. But
it will fail if the version is "1.8.10".
Treating the version number as a string works well enough, until it
doesn't. The above code will do the right thing for Ruby 1.8.6, 1.8.7,
1.8.8, and 1.9.1. But it will fail if the version is "1.8.10".

There are a few version number classes out there that do better than
treating version numbers as plain strings. Perhaps the most well known and
often used is Gem::Version, part of rubygems. This class separates the
version into fields and lets you manipulate and compare version numbers
more robustly. It provides limited support for "prerelease" versions
through using string-valued fields. However, it's still a little clumsy.
A prerelease version has to be represented like this: "1.9.2.b.1" or
"1.9.2.preview.2". Wouldn't it be nice to be able to parse more typical
version number formats such as "1.9.2b1" and "1.9.2 preview-2"?

Yes you can!
treating version numbers as plain strings. One well-known class is
Gem::Version, part of rubygems. This class separates the version into
fields and lets you manipulate and compare version numbers more robustly.
It provides limited support for "prerelease" versions through using
string-valued fields as a bit of a hack. However, it's still a little
clumsy. A prerelease version has to be represented like this: "1.9.2.b.1"
or "1.9.2.preview.2". Wouldn't it be nice to be able to parse more typical
version number formats such as "1.9.2b1" and "1.9.2 preview-2"? Wouldn't
it be nice for a version like "1.9.2b1" to _understand_ that it's a "beta"
version and behave accordingly?

With Versionomy, you can!

=== Some examples

require 'versionomy'

v1 = Versionomy.parse('1.3.2')
# Create version numbers that understand their own semantics
v1 = Versionomy.create(:major => 1, :minor => 3, :tiny => 2)
v1.major # => 1
v1.minor # => 3
v1.tiny # => 2
v1.release_type # => :final
v1.patchlevel # => 0

# Parse version numbers, including common prerelease syntax
v2 = Versionomy.parse('1.4a3')
v2.major # => 1
v2.minor # => 4
v2.tiny # => 0
v2.release_type # => :alpha
v2.alpha_version # => 3
v2 > v1 # => true
v2.to_s # => '1.4a3'

# Another parsing example.
v3 = Versionomy.parse('1.4.0b2')
v3.major # => 1
v3.minor # => 4
Expand All @@ -56,42 +64,57 @@ Yes you can!
v3 > v2 # => true
v3.to_s # => '1.4.0b2'

v4 = v3.bump(:beta_version)
# You can bump any field
v4 = Versionomy.parse('1.4.0b2').bump(:beta_version)
v4.to_s # => '1.4.0b3'
v5 = v4.bump(:tiny)
v5.to_s # => '1.4.1'

v5 = v4.bump(:release_type)
v5.to_s # => '1.4.0rc1'

v6 = v5.bump(:release_type)
v6.to_s # => '1.4.0'

v7 = v3.bump(:tiny)
v7.to_s # => '1.4.1'
# Bumping the release type works as you would expect
v6 = Versionomy.parse('1.4.0b2').bump(:release_type)
v6.release_type # => :release_candidate
v6.to_s # => '1.4.0rc1'
v7 = v6.bump(:release_type)
v7.release_type # => :final
v7.to_s # => '1.4.0'

v8 = v3.bump(:major)
# If a version has trailing zeros, it remembers how many fields to
# unparse; however, you can also change this.
v8 = Versionomy.parse('1.4.0b2').bump(:major)
v8.to_s # => '2.0.0'
v8.unparse(:optional_fields => [:tiny]) # => '2.0'
v8.unparse(:required_fields => [:tiny2]) # => '2.0.0.0'

# Comparisons are semantic, so will behave as expected even if the
# formatting is set up differently.
v9 = Versionomy.parse('2.0.0.0')
v9.to_s # => '2.0.0.0'
v9 == v8 # => true
v9 == Versionomy.parse('2') # => true

v10 = v8.bump(:patchlevel)
# Patchlevels are supported when the release type is :final
v10 = Versionomy.parse('2.0.0').bump(:patchlevel)
v10.patchlevel # => 1
v10.to_s # => '2.0.0-1'
v11 = Versionomy.parse('2.0p1')
v11.patchlevel # => 1
v11.to_s # => '2.0p1'
v11 == v10 # => true

# You can create your own format from scratch or by modifying an
# existing format
microsoft_format = Versionomy.default_format.modified_copy do
field(:minor) do
recognize_number(:default_value_optional => true,
:delimiter_regexp => '\s?sp',
:default_delimiter => ' SP')
end
end
v11 = microsoft_format.parse('2008 SP2')
v11.major # => 2008
v11.minor # => 2
v11.tiny # => 0
v11.to_s # => '2008 SP2'
v11 == Versionomy.parse('2008.2') # => true
v12 = microsoft_format.parse('2008 SP2')
v12.major # => 2008
v12.minor # => 2
v12.tiny # => 0
v12.to_s # => '2008 SP2'
v12 == Versionomy.parse('2008.2') # => true

=== Feature list

Expand All @@ -107,7 +130,9 @@ custom formats.

Finally, Versionomy also lets you to create alternate versioning "schemas".
You can define any number of version number fields, and provide your own
semantics for comparing, parsing, and modifying version numbers.
semantics for comparing, parsing, and modifying version numbers. You can
provide conversions from one schema to another. As an example, Versionomy
provides a schema and formatter/parser matching Gem::Version.

=== Requirements

Expand Down
4 changes: 3 additions & 1 deletion lib/versionomy/format/rubygems.rb
Expand Up @@ -43,7 +43,9 @@ module Format
# This is identical to calling <tt>get('rubygems')</tt>.
#
# The rubygems format is designed to be parse-compatible with the
# Gem::Version class used in rubygems.
# Gem::Version class used in rubygems. The only caveat is, whereas
# Gem::Version handles an arbitrary number of fields, this format is
# limited to a maximum of 8.
#
# For the exact annotated definition of the rubygems schema and format,
# see the source code for Versionomy::Format::Rubygems#create.
Expand Down
15 changes: 9 additions & 6 deletions lib/versionomy/schema.rb
Expand Up @@ -43,17 +43,20 @@ module Versionomy
# The schema controls what fields are present in the version, how
# version numbers are compared, what the default values are, and how
# values can change. Version numbers with the same schema can be
# compared with one another, and version numbers can be converted to
# formats that share the same schema.
# compared with one another, and version numbers can be converted
# trivially to formats that share the same schema, without requiring a
# Conversion implementation.
#
# At its simplest, a version number is defined as a sequence of fields,
# each with a name and data type. These fields may be integer-valued,
# string-valued, or symbolic, though most will probably be integers.
# Symbolic fields are useful, for example, if you want a field to specify
# the type of prerelease (e.g. "alpha", "beta", or "release candidate").
# Symbolic fields are enumerated types that are useful, for example, if
# you want a field to specify the type of prerelease (e.g. "alpha",
# "beta", or "release candidate").
#
# As a simple example, you could construct a schema for versions numbers
# of the form "major.minor.tiny" like this:
# As a simple conceptual example, you could construct a schema for
# version numbers of the form "major.minor.tiny" like this. (This is a
# conceptual diagram, not actual syntax.)
#
# ("major": integer), ("minor": integer), ("tiny": integer)
#
Expand Down
7 changes: 4 additions & 3 deletions lib/versionomy/schema/wrapper.rb
Expand Up @@ -90,7 +90,7 @@ def to_s # :nodoc:
# Returns true if this schema is equivalent to the other schema.
# Two schemas are equivalent if their root fields are the same--
# which means that the entire field tree is the same-- and they
# include the same modules.
# include the same value modules.
# Note that this is different from the definition of <tt>==</tt>.

def eql?(obj_)
Expand All @@ -100,8 +100,9 @@ def eql?(obj_)


# Returns true if this schema is compatible with the other schema.
# Two schemas are equivalent if their root fields are the same--
# which means that the entire field tree is the same.
# Two schemas are compatible if their root fields are the same--
# which means that the entire field tree is the same. They may,
# however, include different value modules.
# Note that this is different from the definition of <tt>eql?</tt>.

def ==(obj_)
Expand Down
63 changes: 47 additions & 16 deletions lib/versionomy/value.rb
Expand Up @@ -153,6 +153,8 @@ def marshal_load(data_) # :nodoc:
yaml_as "tag:danielazuma.com,2009:version"


# Deserialize a version number from YAML

def self.yaml_new(klass_, tag_, data_) # :nodoc:
unless data_.kind_of?(::Hash)
raise ::YAML::TypeError, "Invalid version format: #{val_.inspect}"
Expand All @@ -167,7 +169,9 @@ def self.yaml_new(klass_, tag_, data_) # :nodoc:
end


def to_yaml(opts_={}) # :nodoc:
# Serialize this version number to YAML format.

def to_yaml(opts_={})
data_ = marshal_dump
::YAML::quick_emit(nil, opts_) do |out_|
out_.map(taguri, to_yaml_style) do |map_|
Expand All @@ -184,7 +188,7 @@ def to_yaml(opts_={}) # :nodoc:
end


# Unparse this version number.
# Unparse this version number and return a string.
#
# Raises Versionomy::Errors::UnparseError if unparsing failed.

Expand All @@ -193,14 +197,16 @@ def unparse(params_=nil)
end


# Return the schema defining the form of this version number
# Return the schema defining the structure and semantics of this
# version number.

def schema
@format.schema
end


# Return the format defining the form of this version number
# Return the format defining the schema and formatting/parsing of
# this version number.

def format
@format
Expand Down Expand Up @@ -235,6 +241,8 @@ def each_field_object # :nodoc:


# Returns an array of recognized field names for this value, in field order.
# This is the order of the fields actually present in this value, in
# order from most to least significant.

def field_names
@field_path.map{ |field_| field_.name }
Expand Down Expand Up @@ -270,6 +278,8 @@ def [](field_)


# Returns the value as an array of field values, in field order.
# This is the order of the fields actually present in this value, in
# order from most to least significant.

def values_array
@field_path.map{ |field_| @values[field_.name] }
Expand All @@ -285,15 +295,15 @@ def values_hash

# Returns a new version number created by bumping the given field. The
# field may be specified as a field object, field name, or field index.
# Returns self if value could not be changed.
# Returns nil if the field was not recognized.
# Returns self unchanged if the field was not recognized or could not
# be modified.

def bump(name_)
name_ = @field_path[name_] if name_.kind_of?(::Integer)
name_ = name_.name if name_.kind_of?(Schema::Field)
return nil unless name_
return self unless name_
name_ = name_.to_sym
return nil unless @values.include?(name_)
return self unless @values.include?(name_)
values_ = []
@field_path.each do |field_|
oldval_ = @values[field_.name]
Expand All @@ -310,15 +320,26 @@ def bump(name_)
end


# Returns a new version number created by changing the given field values.
# Returns a new version number created by cloning this version number
# and changing the given field values.
#
# You should pass in a hash of field names to values. These are the
# fields to modify; any other fields will be left alone, unless they
# are implicitly changed by the modifications you are making.
# For example, changing the :release_type on a value using the standard
# format, may change which fields are present in the resulting value.
#
# You may also pass a delta hash to modify the unparse params stored in
# the value.

def change(values_={}, unparse_params_={})
unparse_params_ = @unparse_params.merge(unparse_params_) if @unparse_params
Value.new(@values.merge(values_), @format, unparse_params_)
end


# Returns this value converted to the given format.
# Attempts to convert this value to the given format, and returns the
# resulting value.
#
# Raises Versionomy::Errors::ConversionError if the value could not
# be converted.
Expand All @@ -343,8 +364,9 @@ def hash # :nodoc:
end


# Returns true if this version number is equal to the given verison number.
# This type of equality means the schemas and values are the same.
# Returns true if this version number is equivalent to the given number.
# This type of equality means their schemas are compatible and their
# field values are equal.
# Note that this is different from the definition of <tt>==</tt>.

def eql?(obj_)
Expand All @@ -361,17 +383,22 @@ def eql?(obj_)
end


# Returns true if this version number is equal to the given verison number.
# This type of equality means that the values are the same, possibly after
# suitable automatic conversion of the RHS.
# Returns true if this version number is value-equal to the given number.
# This type of equality means that they are equivalent, or that it is
# possible to convert the RHS to the LHS's format, and that they would
# be equivalent after such a conversion has taken place.
# Note that this is different from the definition of <tt>eql?</tt>.

def ==(obj_)
(self <=> obj_) == 0
end


# Compare this version number with the given version number.
# Compare this version number with the given version number,
# returning 0 if the two are value-equal, a negative number if the RHS
# is greater, or a positive number if the LHS is greater.
# The comparison may succeed even if the two have different schemas,
# if the RHS can be converted to the LHS's format.

def <=>(obj_)
if obj_.kind_of?(::String)
Expand All @@ -394,6 +421,8 @@ def <=>(obj_)


# Compare this version number with the given version number.
# The comparison may succeed even if the two have different schemas,
# if the RHS can be converted to the LHS's format.

def <(obj_)
val_ = (self <=> obj_)
Expand All @@ -405,6 +434,8 @@ def <(obj_)


# Compare this version number with the given version number.
# The comparison may succeed even if the two have different schemas,
# if the RHS can be converted to the LHS's format.

def >(obj_)
val_ = (self <=> obj_)
Expand Down
2 changes: 1 addition & 1 deletion tests/tc_readme_examples.rb
Expand Up @@ -68,7 +68,7 @@ def test_readme_file
# a buffer to run all at once, because it might be code that
# gets spread over multiple lines.
delim_index_ = line_.index(' # ')
unless delim_index_
if !delim_index_ || line_[0, delim_index_].strip.length == 0
buffer_start_line_ ||= io_.lineno
buffer_ << line_
next
Expand Down

0 comments on commit 014f6c9

Please sign in to comment.