[DRE-commits] [ruby-versionomy] 01/01: Imported Upstream version 0.4.4

Ben Armstrong synrg at alioth.debian.org
Sun Aug 11 19:39:15 UTC 2013


This is an automated email from the git hooks/post-receive script.

synrg pushed a commit to annotated tag upstream/0.4.4
in repository ruby-versionomy.

commit 7d40fed5485ef41df61b9393aa760b471060b770
Author: Ben Armstrong <synrg at debian.org>
Date:   Sun Aug 11 15:22:09 2013 -0300

    Imported Upstream version 0.4.4
---
 History.rdoc                                  |  122 +++
 README.rdoc                                   |  176 +++++
 Version                                       |    1 +
 Versionomy.rdoc                               |  350 +++++++++
 lib/versionomy.rb                             |   51 ++
 lib/versionomy/conversion.rb                  |  154 ++++
 lib/versionomy/conversion/base.rb             |   99 +++
 lib/versionomy/conversion/parsing.rb          |  196 +++++
 lib/versionomy/errors.rb                      |  160 ++++
 lib/versionomy/format.rb                      |  231 ++++++
 lib/versionomy/format/base.rb                 |  145 ++++
 lib/versionomy/format/delimiter.rb            | 1047 +++++++++++++++++++++++++
 lib/versionomy/format_definitions/rubygems.rb |  343 ++++++++
 lib/versionomy/format_definitions/semver.rb   |  272 +++++++
 lib/versionomy/format_definitions/standard.rb |  398 ++++++++++
 lib/versionomy/interface.rb                   |  219 ++++++
 lib/versionomy/schema.rb                      |   95 +++
 lib/versionomy/schema/field.rb                |  509 ++++++++++++
 lib/versionomy/schema/wrapper.rb              |  321 ++++++++
 lib/versionomy/value.rb                       |  564 +++++++++++++
 lib/versionomy/version.rb                     |   45 ++
 metadata.yml                                  |  114 +++
 test/tc_custom_format.rb                      |   66 ++
 test/tc_readme_examples.rb                    |  120 +++
 test/tc_rubygems_basic.rb                     |  245 ++++++
 test/tc_rubygems_conversions.rb               |  190 +++++
 test/tc_semver_basic.rb                       |  213 +++++
 test/tc_semver_conversions.rb                 |  196 +++++
 test/tc_standard_basic.rb                     |  175 +++++
 test/tc_standard_bump.rb                      |  171 ++++
 test/tc_standard_change.rb                    |   98 +++
 test/tc_standard_comparison.rb                |  142 ++++
 test/tc_standard_misc.rb                      |   95 +++
 test/tc_standard_parse.rb                     |  388 +++++++++
 test/tc_standard_reset.rb                     |  105 +++
 test/tc_version_of.rb                         |   81 ++
 36 files changed, 7897 insertions(+)

diff --git a/History.rdoc b/History.rdoc
new file mode 100644
index 0000000..1379bb0
--- /dev/null
+++ b/History.rdoc
@@ -0,0 +1,122 @@
+=== 0.4.4 / 2012-06-27
+
+* Tried to be a little more robust against incomplete psych installations.
+* Travis CI integration.
+
+=== 0.4.3 / 2012-03-27
+
+* Fixed a few warnings.
+
+=== 0.4.2 / 2012-02-17
+
+* Support psych interface for YAML serialization.
+
+=== 0.4.1 / 2011-04-26
+
+* Support underscore "_" as a delimiter.
+* A .gemspec file is now available for gem building and bundler git integration.
+* Some cleanup of the Rakefile and tests.
+
+=== 0.4.0 / 2010-05-24
+
+* Included Semantic Version (http://semver.org/) format called "semver".
+* Parsing conversions can now prescreen or premodify the original format value.
+* Conversion algorithm tries to convert through the standard format if a direct conversion isn't found. For example, there is currently no direct conversion between semver and rubygems formats, but the converter can nevertheless convert the two because both can convert to and from standard.
+* Expanded the module version detection to include VERSION submodules.
+* Some Rakefile fixes to match RDoc and Ruby 1.9 changes.
+
+=== 0.3.0 / 2009-11-30
+
+* Alpha release, opened for public feedback
+* Autoloading of format definitions using a load path.
+* Format and conversion registry/lookup is now thread-safe.
+* Implemented resetting a particular field in the value.
+* Implemented aliases for field names.
+* Changed the canonical YAML tag to its permanent value. The old one will continue to be recognized, but only the permanent one will be written from now on.
+* Documentation updates
+
+=== 0.2.5 / 2009-11-24
+
+* Preserve minimum width of a field, if the field has leading zeros. For example, "2.01".
+
+=== 0.2.4 / 2009-11-19
+
+* Fixed a regression introduced in 0.2.2 where "1.0a" was being recognized as an alpha version rather than a patchlevel of 1, and similar for "1.0b" and "1.0d".
+
+=== 0.2.3 / 2009-11-19
+
+* Recognize "_" and "u" as patchlevel delimiters, to support Sun's Java version numbers (e.g. "1.6.0_17", "6u17").
+* Recognize "v" prefix found on some version numbers (e.g. "v1.2")
+
+=== 0.2.2 / 2009-11-18
+
+* Standard format now supports certain kinds of prereleases without a prerelease number. e.g. "1.9.2dev" is interpreted as the same number as "1.9.2dev0".
+* Added Versionomy#ruby_version.
+* A field can specify a default_value for parsing, distinct from the one specified by the schema.
+* A field can specify requires_next_field to control whether the following field is required or optional.
+
+=== 0.2.1 / 2009-11-08
+
+* Added Versionomy#version_of.
+* Now lets Blockenspiel set its own VERSION instead of reaching into Blockenspiel's namespace.
+
+=== 0.2.0 / 2009-11-05
+
+* API CHANGE: Slight change to value comparison semantics. Value#eql? returns true only if the schemas are the same, whereas Value#== and the greater than and less than comparisons attempt to compare the semantic value, and thus may perform automatic schema conversion on the RHS.
+* API CHANGE: Merged Formats namespace into Format. Versionomy::Formats is now a (deprecated) alias for Versionomy::Format.
+* API CHANGE: The delimiter parsing algorithm now defaults :extra_characters to :error instead of :ignore.
+* Added a mechanism for converting from one format/schema to another.
+* Added Rubygems format, along with conversions to and from the standard format.
+* Values now include Comparable.
+* Values can now be serialized using Marshal and YAML.
+* Schemas can now add custom methods to value objects. Added "prerelease?" and "release" methods to rubygems and standard format values.
+* Added default field settings to schema DSL.
+* Implemented #=== for schemas and formats.
+* Many minor bug fixes and documentation updates.
+
+=== 0.1.3 / 2009-10-29
+
+* Fixed an issue with parsing the "-p" patchlevel delimiter, e.g. "1.9.1-p243". (Reported by Luis Lavena.)
+
+=== 0.1.2 / 2009-10-28
+
+* You can now specify fields by index in methods of Versionomy::Value.
+* Minor rakefile and documentation updates.
+
+=== 0.1.1 / 2009-10-19
+
+* Formats can now be specified by name in the convenience interface.
+* FormatRedefinedError no longer subclasses FormatCreationError.
+* Some documentation updates.
+* Rakefile updates for publishing to rubyforge and gemcutter.
+
+=== 0.1.0 / 2009-10-14
+
+* General rearchitecture. Better distinction between format and schema. Schema split into schema and field objects so the API makes more sense. Values are tighter and easier to use. Formats can now be built using a DSL. A bunch of API changes and bug fixes accompanied this-- too many to list.
+* In the standard schema, renamed release type "release" to "final". Also renamed release type "prerelease" to "preview", now sorted between "release candidate" and "final".
+* Documentation is much more complete.
+* Now tested and confirmed compatible with Matz Ruby 1.9.1 and JRuby 1.4.
+* Now uses blockenspiel 0.2; thus longer requires the mixology gem.
+* Building no longer requires hoe.
+
+=== 0.0.4 / 2008-10-24
+
+* Fixed incompatibility with Blockenspiel 0.0.4
+* Fixed a number of issues with remembering value parse settings
+* Parser recognizes additional release type formats
+* Values have a parse method to parse another string in the same form
+* Implemented comparison between value and string
+* Exceptions correctly raised on comparison between incompatible types
+
+=== 0.0.3 / 2008-10-21
+
+* Fixed string representations (inspect method)
+* Fixed up equality and hash computation for version values
+
+=== 0.0.2 / 2008-10-20
+
+* Fixed manifest
+
+=== 0.0.1 / 2008-10-20
+
+* Initial test release
diff --git a/README.rdoc b/README.rdoc
new file mode 100644
index 0000000..e5f793d
--- /dev/null
+++ b/README.rdoc
@@ -0,0 +1,176 @@
+== Versionomy
+
+Versionomy is a generalized version number library.
+It provides tools to represent, manipulate, parse, and compare version
+numbers in the wide variety of versioning schemes in use.
+
+This document summarizes the features of Versionomy with a quick synopsis
+and feature list. For more detailed usage information and examples, see
+{Versionomy.rdoc}[link:Versionomy\_rdoc.html].
+
+=== Some examples
+
+ require 'versionomy'
+ 
+ # 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'
+ 
+ # Version numbers are semantically self-adjusting.
+ v3 = Versionomy.parse('1.4.0b2')
+ v3.major                                 # => 1
+ v3.minor                                 # => 4
+ v3.tiny                                  # => 0
+ v3.release_type                          # => :beta
+ v3.alpha_version                         # raises NoMethodError
+ v3.beta_version                          # => 2
+ v3 > v2                                  # => true
+ v3.to_s                                  # => '1.4.0b2'
+ 
+ # 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'
+ 
+ # 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'
+ 
+ # 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 == Versionomy.parse('2')              # => true
+ 
+ # 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
+ 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
+
+Versionomy's default versioning scheme handles four primary fields (labeled
++major+, +minor+, +tiny+, and +tiny2+). It also supports prerelease versions
+such as preview, development, alpha, beta, and release candidate. Finally,
+it supports patchlevel numbers for released versions.
+
+Versionomy can compare any two version numbers with compatible structure,
+and "bump" versions at any level. It supports parsing and unparsing in most
+commonly-used formats, and allows you to extend the parsing to include
+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. You can
+provide conversions from one schema to another. As an example, Versionomy
+provides a schema and formatter/parser matching Gem::Version.
+
+=== Requirements
+
+* Ruby 1.8.7 or later, Ruby 1.9.1 or later, JRuby 1.5 or later, or
+  Rubinius 1.0 or later.
+* blockenspiel 0.4.5 or later.
+
+=== Installation
+
+ gem install versionomy
+
+=== Known issues and limitations
+
+* Test coverage is still a little skimpy. It is focused on the "standard"
+  version number format and schema, but doesn't fully exercise all the
+  capabilities of custom formats.
+
+=== Development and support
+
+Documentation is available at http://dazuma.github.com/versionomy/rdoc
+
+Source code is hosted on Github at http://github.com/dazuma/versionomy
+
+Contributions are welcome. Fork the project on Github.
+
+Build status: {<img src="https://secure.travis-ci.org/dazuma/versionomy.png" />}[http://travis-ci.org/dazuma/versionomy]
+
+Report bugs on Github issues at http://github.org/dazuma/versionomy/issues
+
+Contact the author at dazuma at gmail dot com.
+
+=== Author / Credits
+
+Versionomy is written by Daniel Azuma (http://www.daniel-azuma.com/).
+
+== LICENSE:
+
+Copyright 2008-2012 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.
diff --git a/Version b/Version
new file mode 100644
index 0000000..6f2743d
--- /dev/null
+++ b/Version
@@ -0,0 +1 @@
+0.4.4
diff --git a/Versionomy.rdoc b/Versionomy.rdoc
new file mode 100644
index 0000000..64a2429
--- /dev/null
+++ b/Versionomy.rdoc
@@ -0,0 +1,350 @@
+== Versionomy
+
+Versionomy is a generalized version number library.
+It provides tools to represent, manipulate, parse, and compare version
+numbers in the wide variety of versioning schemes in use.
+
+This document provides a step-by-step introduction to most of the features
+of Versionomy.
+
+=== Version numbers done right?
+
+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
+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 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" or "1.10".
+And properly interpreting "prerelease" version syntax such as
+"1.9.2-preview1"? Forget it.
+
+There are a few version number classes out there that do better than
+treating version numbers as plain strings. One example is Gem::Version,
+part of the RubyGems package. This class separates the version into fields
+and lets you manipulate and compare version numbers more robustly. It even
+provides limited support for "prerelease" versions through using string-
+valued fields-- although it's a hack, and a bit of a clumsy one at that. 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-preview2"? 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 do all this and more. Here's how...
+
+=== Creating version numbers
+
+Creating a version number object in Versionomy is as simple as passing a
+string to a factory. Versionomy understands a wide range of version number
+formats out of the box.
+
+ v1 = Versionomy.parse('1.2')             # Simple version numbers
+ v2 = Versionomy.parse('2.1.5.0')         # Up to four fields supported
+ v3 = Versionomy.parse('1.9b3')           # Alpha and beta versions
+ v4 = Versionomy.parse('1.9rc2')          # Release candidates too
+ v5 = Versionomy.parse('1.9.2-preview2')  # Preview releases
+ v6 = Versionomy.parse('1.9.2-p6')        # Patchlevels
+ v7 = Versionomy.parse('v2.0 beta 6.1')   # Many alternative syntaxes
+
+You can also construct version numbers manually by passing a hash of field
+values. See the next section for a discussion of fields.
+
+ v1 = Versionomy.create(:major => 1, :minor => 2)    # creates version "1.2"
+ v2 = Versionomy.create(:major => 1, :minor => 9,
+        :release_type => :beta, :beta_version => 3)  # creates version "1.9b3"
+
+The current ruby virtual machine version can be obtained using:
+
+ v1 = Versionomy.ruby_version
+
+Many other libraries include their version as a string constant in their
+main namespace module. Versionomy provides a quick facility to attempt to
+extract the version of a library:
+
+ require 'nokogiri'
+ v1 = Versionomy.version_of(Nokogiri)
+
+=== Version number fields
+
+A version number is a collection of fields in a particular order. Standard
+version numbers have the following fields:
+
+* :major
+* :minor
+* :tiny
+* :tiny2
+* :release_type
+
+The first four fields correspond to the four numeric fields of the version
+number. E.g. version numbers have the form "major.minor.tiny.tiny2".
+Trailing fields that have a zero value may be omitted from a string
+representation, but are still present in the Versionomy::Value object.
+
+The fifth field is special. Its value is one of the following symbols:
+
+* :development
+* :alpha
+* :beta
+* :release_candidate
+* :preview
+* :final
+
+The value of the :release_type field determines which other fields are
+available in the version number. If the :release_type is :development, then
+the two fields :development_version and :development_minor are available.
+Similarly, if :release_type is :alpha, then the two fields :alpha_version
+and :alpha_minor are available, and so on. If :release_type is :final, that
+exposes the two fields :patchlevel and :patchlevel_minor.
+
+You can query a field value simply by calling a method on the value:
+
+ v1 = Versionomy.parse('1.2b3')
+ v1.major                        # => 1
+ v1.minor                        # => 2
+ v1.tiny                         # => 0
+ v1.tiny2                        # => 0
+ v1.release_type                 # => :beta
+ v1.beta_version                 # => 3
+ v1.beta_minor                   # => 0
+ v1.release_candidate_version    # raises NoMethodError
+
+The above fields are merely the standard fields that Versionomy provides
+out of the box. Versionomy also provides advanced users the ability to
+define new version "schemas" with any number of different fields and
+different semantics. See the RDocs for Versionomy::Schema for more
+information.
+
+=== Version number calculations
+
+Version numbers can be compared (and thus sorted). Versionomy knows how to
+handle prerelease versions and patchlevels correctly. It also compares the
+semantic value so even if versions use an alternate syntax, they will be
+compared correctly. Each of these expressions evaluates to true:
+
+ Versionomy.parse('1.2') < Versionomy.parse('1.10')
+ Versionomy.parse('1.2') > Versionomy.parse('1.2b3')
+ Versionomy.parse('1.2b3') > Versionomy.parse('1.2a4')
+ Versionomy.parse('1.2') < Versionomy.parse('1.2-p1')
+ Versionomy.parse('1.2') == Versionomy.parse('1.2-p0')
+ Versionomy.parse('1.2b3') == Versionomy.parse('1.2.0-beta3')
+
+Versionomy automatically converts (parses) strings when comparing with a
+version number, so you could even evaluate these:
+
+ Versionomy.parse('1.2') < '1.10'
+ Versionomy::VERSION > '0.2'
+
+The Versionomy API provides various methods for manipulating fields such as
+bumping, resetting to default, and changing to an arbitrary value. Version
+numbers are always immutable, so changing a version number always produces
+a copy. Below are a few examples. See the RDocs for the class
+Versionomy::Value for more details.
+
+ v_orig = Versionomy.parse('1.2b3')
+ v1 = v_orig.change(:beta_version => 4)  # creates version "1.2b4"
+ v2 = v_orig.change(:tiny => 4)          # creates version "1.2.4b3"
+ v3 = v_orig.bump(:minor)                # creates version "1.3"
+ v4 = v_orig.bump(:release_type)         # creates version "1.2rc1"
+ v5 = v_orig.reset(:minor)               # creates version "1.0"
+
+A few more common calculations are also provided:
+
+ v_orig = Versionomy.parse('1.2b3')
+ v_orig.prerelease?                  # => true
+ v6 = v_orig.release                 # creates version "1.2"
+
+=== Parsing and unparsing
+
+Versionomy's parsing and unparsing services appear simple from the outside,
+but a closer look reveals some sophisticated features. Parsing is as simple
+as passing a string to Versionomy#parse, and unparsing is as simple as
+calling Versionomy::Value#unparse or Versionomy::Value#to_s.
+
+ v = Versionomy.parse('1.2b3')  # Create a Versionomy::Value
+ v.unparse                      # => "1.2b3"
+
+Versionomy does its best to preserve the original syntax when parsing a
+version string, so that syntax can be used when unparsing.
+
+ v1 = Versionomy.parse('1.2b3')
+ v2 = Versionomy.parse('1.2.0-beta3')
+ v1 == b2                              # => true
+ v1.unparse                            # => "1.2b3"
+ v2.unparse                            # => "1.2.0-beta3"
+
+Versionomy even preserves the original syntax when changing a value:
+
+ v1 = Versionomy.parse('1.2b3')
+ v2 = Versionomy.parse('1.2.0.0b3')
+ v1 == v2                            # => true
+ v1r = v1.release
+ v2r = v2.release
+ v1r == v2r                          # => true
+ v1r.unparse                         # => "1.2"
+ v2r.unparse                         # => "1.2.0.0"
+
+You can change the settings manually when unparsing a value.
+
+ v1 = Versionomy.parse('1.2b3')
+ v1.unparse                                # => "1.2b3"
+ v1.unparse(:required_fields => :tiny)     # => "1.2.0b3"
+ v1.unparse(:release_type_delim => '-',
+            :release_type_style => :long)  # => "1.2-beta3"
+
+Versionomy also supports serialization using Marshal and YAML.
+
+ require 'yaml'
+ v1 = Versionomy.parse('1.2b3')
+ v1.unparse                      # => "1.2b3"
+ str = v1.to_yaml
+ v2 = YAML.load(str)
+ v2.unparse                      # => "1.2b3"
+
+=== Customized formats
+
+Although the standard parser used by Versionomy is likely sufficient for
+most common syntaxes, Versionomy also lets you customize the parser for an
+unusual syntax. Here is an example of a customized formatter for version
+numbers used by a certain large software company:
+
+ year_sp_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
+ v1 = year_sp_format.parse('2008 SP2')
+ v1.major                               # => 2008
+ v1.minor                               # => 2
+ v1.unparse                             # => "2008 SP2"
+ v1 == "2008.2"                         # => true
+ v2 = v1.bump(:minor)
+ v2.unparse                             # => "2008 SP3"
+
+The above example uses a powerful DSL provided by Versionomy to create a
+specialized parser. In most cases, this DSL will be powerful enough to
+handle your parsing needs; in fact Versionomy's entire standard parser is
+written using the DSL. However, in case you need to parse very unusual
+syntax, you can also write an arbitrary parser. See the RDocs for the
+Versionomy::Format::Delimiter class for more information on the DSL. See
+the RDocs for the Versionomy::Format::Base class for information on the
+interface you need to implement to write an arbitrary parser.
+
+If you create a format, you can register it with Versionomy and provide a
+name for it. This will allow you to reference it easily, as well as allow
+Versionomy to serialize versions created with your custom format. See the
+RDocs for the Versionomy::Format module for more information.
+
+ Versionomy::Format.register("bigcompany.versionformat", year_sp_format)
+ v1 = Versionomy.parse("2009 SP1", "bigcompany.versionformat")
+
+Note that versions in the year_sp_format example can be compared with
+versions using the standard parser. This is because the versions actually
+share the same schema-- that is, they have the same fields. We have merely
+changed the parser.
+
+Recall that it is also possible to change the schema (the fields). This is
+also done via a DSL (see the Versionomy::Schema module and its contents).
+Version numbers with different schemas cannot normally be compared, because
+they have different fields and different semantics. You can, however,
+define ways to convert version numbers from one schema to another. See the
+Versionomy::Conversion module and its contents for details.
+
+Versionomy provides an example of a custom schema with its own custom
+format, designed to mimic the Rubygems version class. This can be accessed
+using the format registered under the name "rubygems". Conversion functions
+are also provided between the rubygems and standard schemas.
+
+ v1 = Versionomy.parse("1.2b3")               # Standard schema/format
+ v2 = Versionomy.parse("1.2.b.4", :rubygems)  # Rubygems schema/format
+ v2.field0                                    # => 1
+                                              #   (Rubygems fields have different names)
+ v1a = v1.convert(:rubygems)                  # creates rubygems version "1.2.b.3"
+ v2a = v2.convert(:standard)                  # creates standard version "1.2b4"
+ v1 < v2                                      # => true
+                                              #   (Schemas are different but Versionomy
+                                              #   autoconverts if possible)
+ v2 < v1                                      # => false
+ v3 = Versionomy.parse("1.2.foo", :rubygems)  # rubygems schema/format
+ v3a = v3.convert(:standard)                  # raises Versionomy::Errors::ConversionError
+                                              #   (Value not convertable to standard)
+ v1 < v3                                      # raises Versionomy::Errors::SchemaMismatchError
+                                              #   (Autoconversion failed)
+ v3 > v1                                      # => true
+                                              #   (Autoconversion is attempted only on the
+                                              #   the second value, and this one succeeds.)
+
+The APIs for defining schemas, formats, and conversions are rather complex.
+I recommend looking through the examples in the modules
+Versionomy::Format::Standard, Versionomy::Format::Rubygems, and
+Versionomy::Conversion::Rubygems for further information.
+
+=== Requirements
+
+* Ruby 1.8.7 or later, Ruby 1.9.1 or later, JRuby 1.5 or later, or
+  Rubinius 1.0 or later.
+* blockenspiel 0.4.5 or later.
+
+=== Installation
+
+ gem install versionomy
+
+=== Known issues and limitations
+
+* Test coverage is still a little skimpy. It is focused on the "standard"
+  version number format and schema, but doesn't fully exercise all the
+  capabilities of custom formats.
+
+=== Development and support
+
+Documentation is available at http://dazuma.github.com/versionomy/rdoc
+
+Source code is hosted on Github at http://github.com/dazuma/versionomy
+
+Contributions are welcome. Fork the project on Github.
+
+Build status: {<img src="https://secure.travis-ci.org/dazuma/versionomy.png" />}[http://travis-ci.org/dazuma/versionomy]
+
+Report bugs on Github issues at http://github.org/dazuma/versionomy/issues
+
+Contact the author at dazuma at gmail dot com.
+
+=== Author / Credits
+
+Versionomy is written by Daniel Azuma (http://www.daniel-azuma.com/).
+
+== LICENSE:
+
+Copyright 2008-2012 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.
diff --git a/lib/versionomy.rb b/lib/versionomy.rb
new file mode 100644
index 0000000..acc782e
--- /dev/null
+++ b/lib/versionomy.rb
@@ -0,0 +1,51 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy entry point
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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.
+# -----------------------------------------------------------------------------
+;
+
+
+require 'blockenspiel'
+
+require 'versionomy/errors'
+require 'versionomy/schema'
+require 'versionomy/schema/field'
+require 'versionomy/schema/wrapper'
+require 'versionomy/format'
+require 'versionomy/format/base'
+require 'versionomy/format/delimiter'
+require 'versionomy/value'
+require 'versionomy/conversion'
+require 'versionomy/conversion/base'
+require 'versionomy/conversion/parsing'
+require 'versionomy/interface'
+require 'versionomy/version'
diff --git a/lib/versionomy/conversion.rb b/lib/versionomy/conversion.rb
new file mode 100644
index 0000000..8c36d5b
--- /dev/null
+++ b/lib/versionomy/conversion.rb
@@ -0,0 +1,154 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy conversion interface and registry
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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.
+# -----------------------------------------------------------------------------
+;
+
+
+require 'thread'
+
+
+module Versionomy
+
+
+  # === Conversion between version schemas.
+  #
+  # Conversions are algorithms for converting from one schema to another.
+  # This is useful for performing conversions as well as comparing version
+  # numbers that use different schemas.
+  #
+  # To implement a conversion algorithm, implement the API defined by
+  # Versionomy::Conversion::Base. Then, register your conversion by calling
+  # Versionomy::Conversion#register. You will need to specify which schemas
+  # (from and to) that your conversion should handle. From that point on,
+  # whenever Versionomy needs to convert a value between those two schemas,
+  # it will use your conversion. You can register the same conversion object
+  # for multiple pairs of schemas, but you can register only one conversion
+  # object for any pair.
+  #
+  # A common technique for doing conversions is to unparse the version to a
+  # string, and then parse it in the new format. Versionomy provides a tool,
+  # Versionomy::Conversion::Parsing, for performing such conversions. The
+  # conversions between the standard and rubygems formats uses this tool.
+  # See Versionomy::Conversion::Rubygems for annotated examples.
+
+  module Conversion
+
+    @registry = {}
+    @mutex = ::Mutex.new
+
+    class << self
+
+
+      # Convert the given value to the given format. This is identical to
+      # calling <tt>value_.convert(format_, convert_params_)</tt>.
+      #
+      # The format may be specified as a format object or as the name of a
+      # format in the Format registry.
+      #
+      # Raises Versionomy::Errors::ConversionError if the value could not
+      # be converted.
+
+      def convert(value_, format_, convert_params_=nil)
+        value_.convert(format_, convert_params_)
+      end
+
+
+      # Get a conversion capable of converting between the given schemas.
+      #
+      # The schemas may be specified as format names, Format objects,
+      # schema wrapper objects, or the root field of the schema.
+      #
+      # If strict is set to false, returns nil if no such conversion could
+      # be found. If strict is set to true, may raise one of these errors:
+      #
+      # Raises Versionomy::Errors::UnknownFormatError if a format was
+      # specified by name but the name is not known.
+      #
+      # Raises Versionomy::Errors::UnknownConversionError if the formats
+      # were recognized but no conversion was found to handle them.
+
+      def get(from_schema_, to_schema_, strict_=false)
+        key_ = _get_key(from_schema_, to_schema_)
+        conversion_ = @mutex.synchronize{ @registry[key_] }
+        if strict_ && conversion_.nil?
+          raise Errors::UnknownConversionError
+        end
+        conversion_
+      end
+
+
+      # Register the given conversion as the handler for the given schemas.
+      #
+      # The schemas may be specified as format names, Format objects,
+      # schema wrapper objects, or the root field of the schema.
+      #
+      # Raises Versionomy::Errors::ConversionRedefinedError if a conversion
+      # has already been registered for the given schemas.
+      #
+      # Raises Versionomy::Errors::UnknownFormatError if a format was
+      # specified by name but the name is not known.
+
+      def register(from_schema_, to_schema_, conversion_, silent_=false)
+        key_ = _get_key(from_schema_, to_schema_)
+        @mutex.synchronize do
+          if @registry.include?(key_)
+            unless silent_
+              raise Errors::ConversionRedefinedError
+            end
+          else
+            @registry[key_] = conversion_
+          end
+        end
+      end
+
+
+      private
+
+      def _get_key(from_schema_, to_schema_)  # :nodoc:
+        [_get_schema(from_schema_), _get_schema(to_schema_)]
+      end
+
+      def _get_schema(schema_)  # :nodoc:
+        schema_ = Format.get(schema_, true) if schema_.kind_of?(::String) || schema_.kind_of?(::Symbol)
+        schema_ = schema_.schema if schema_.respond_to?(:schema)
+        schema_ = schema_.root_field if schema_.respond_to?(:root_field)
+        schema_
+      end
+
+
+    end
+
+  end
+
+
+end
diff --git a/lib/versionomy/conversion/base.rb b/lib/versionomy/conversion/base.rb
new file mode 100644
index 0000000..f660f6c
--- /dev/null
+++ b/lib/versionomy/conversion/base.rb
@@ -0,0 +1,99 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy conversion base class
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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 Conversion
+
+
+    # The base conversion class.
+    #
+    # This base class defines the API for a conversion. All conversions must
+    # define the method <tt>convert_value</tt> documented here. Conversions
+    # need not actually extend this base class, as long as they duck-type
+    # this method. However, this base class does provide a few convenience
+    # methods such as a sane implementation of inspect.
+
+    class Base
+
+
+      # Create a conversion using a simple DSL.
+      # You can pass a block to the initializer that takes the same
+      # parameters as convert_value, and the conversion will use that block
+      # to perform the conversion.
+
+      def initialize(&block_)
+        @_converter = block_
+      end
+
+
+      # Convert the given value to the given format and return the converted
+      # value.
+      #
+      # The convert_params may be interpreted however the particular
+      # conversion wishes.
+      #
+      # Raises Versionomy::Errors::ConversionError if the conversion failed.
+
+      def convert_value(value_, format_, convert_params_=nil)
+        if @_converter
+          @_converter.call(value_, format_, convert_params_)
+        else
+          raise Errors::ConversionError, "Conversion not implemented"
+        end
+      end
+
+
+      # Inspect this conversion.
+
+      def inspect
+        "#<#{self.class}:0x#{object_id.to_s(16)}>"
+      end
+
+
+      # The default to_s implementation just calls inspect.
+
+      def to_s
+        inspect
+      end
+
+
+    end
+
+
+  end
+
+end
diff --git a/lib/versionomy/conversion/parsing.rb b/lib/versionomy/conversion/parsing.rb
new file mode 100644
index 0000000..f54cb15
--- /dev/null
+++ b/lib/versionomy/conversion/parsing.rb
@@ -0,0 +1,196 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy conversion base class
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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 Conversion
+
+
+    # A conversion strategy that relies on parsing.
+    # Essentially, it unparses the value and then attempts to parse it with
+    # the new format.
+
+    class Parsing < Base
+
+
+      # Create a parsing conversion.
+      #
+      # By default, this just unparses and reparses using the default
+      # parse settings. In some cases, this may be enough, but you may
+      # wish to improve the reliability of the conversion by tweaking the
+      # parsing settings. To do so, pass a block to the new method, and
+      # call methods of Versionomy::Conversion::Parsing::Builder in that
+      # block.
+
+      def initialize(&block_)
+        if block_
+          builder_ = Builder.new
+          ::Blockenspiel.invoke(block_, builder_)
+          @original_value_modifier = builder_._get_original_value_modifier
+          @string_modifier = builder_._get_string_modifier
+          @unparse_params_modifier = builder_._get_unparse_params_modifier
+          @parse_params_generator ||= builder_._get_parse_params_generator
+        end
+      end
+
+
+      # Returns a value equivalent to the given value in the given format.
+      #
+      # The convert_params are passed to this conversion's customization
+      # blocks (if any).
+      #
+      # Raises Versionomy::Errors::ConversionError if the conversion failed.
+      # Typically, this is due to a failure of the parsing or unparsing.
+
+      def convert_value(value_, format_, convert_params_=nil)
+        begin
+          convert_params_ ||= {}
+          if @original_value_modifier
+            value_ = @original_value_modifier.call(value_, convert_params_)
+          end
+          unparse_params_ = value_.unparse_params || {}
+          if @unparse_params_modifier
+            unparse_params_ = @unparse_params_modifier.call(unparse_params_, convert_params_)
+          end
+          string_ = value_.unparse(unparse_params_)
+          if @string_modifier
+            string_ = @string_modifier.call(string_, convert_params_)
+          end
+          if @parse_params_generator
+            parse_params_ = @parse_params_generator.call(convert_params_)
+          else
+            parse_params_ = nil
+          end
+          new_value_ = format_.parse(string_, parse_params_)
+          return new_value_
+        rescue Errors::UnparseError => ex_
+          raise Errors::ConversionError, "Unparsing failed: #{ex_.inspect}"
+        rescue Errors::ParseError => ex_
+          raise Errors::ConversionError, "Parsing failed: #{ex_.inspect}"
+        end
+      end
+
+
+      # Call methods of this class in the block passed to
+      # Versionomy::Conversion::Parsing#new to fine-tune the behavior of
+      # the converter.
+
+      class Builder
+
+        include ::Blockenspiel::DSL
+
+
+        def initialize  # :nodoc:
+          @original_value_modifier = nil
+          @string_modifier = nil
+          @parse_params_generator = nil
+          @unparse_params_modifier = nil
+        end
+
+
+        # Provide a block that generates the params used to parse the new
+        # value. The block should take one parameter, the convert_params
+        # passed to convert_value (which may be nil). It should return the
+        # parse params that should be used.
+
+        def to_generate_parse_params(&block_)
+          @parse_params_generator = block_
+        end
+
+
+        # Provide a block that can modify the params used to unparse the
+        # old value. The block should take two parameters: first, the
+        # original unparse params from the old value (which may be nil),
+        # and second, the convert_params passed to convert_value (which
+        # may also be nil). It should return the unparse params that
+        # should actually be used.
+
+        def to_modify_unparse_params(&block_)
+          @unparse_params_modifier = block_
+        end
+
+
+        # Provide a block that can modify the original value prior to it
+        # being unparsed. The block should take two parameters: first, the
+        # original value to be converted, and second, the convert_params
+        # passed to convert_value (which may be nil). It should return the
+        # value to be unparsed, which may be the same as the value
+        # originally passed in. This method may fail-fast by raising a
+        # Versionomy::Errors::ConversionError if it determines that the
+        # value passed in cannot be converted as is.
+
+        def to_modify_original_value(&block_)
+          @original_value_modifier = block_
+        end
+
+
+        # Provide a block that can modify the unparsed string prior to
+        # it being passed to the parser. The block should take two
+        # parameters: first, the string resulting from unparsing the old
+        # value, and second, the convert_params passed to convert_value
+        # (which may be nil). It should return the string to be parsed to
+        # get the new value.
+
+        def to_modify_string(&block_)
+          @string_modifier = block_
+        end
+
+
+        def _get_original_value_modifier  # :nodoc:
+          @original_value_modifier
+        end
+
+        def _get_string_modifier  # :nodoc:
+          @string_modifier
+        end
+
+        def _get_unparse_params_modifier  # :nodoc:
+          @unparse_params_modifier
+        end
+
+        def _get_parse_params_generator  # :nodoc:
+          @parse_params_generator
+        end
+
+      end
+
+
+    end
+
+
+  end
+
+end
diff --git a/lib/versionomy/errors.rb b/lib/versionomy/errors.rb
new file mode 100644
index 0000000..5eed499
--- /dev/null
+++ b/lib/versionomy/errors.rb
@@ -0,0 +1,160 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy exceptions
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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
+
+
+  # This is a namespace for errors that can be thrown by Versionomy.
+
+  module Errors
+
+
+    # Base class for all Versionomy exceptions
+
+    class VersionomyError < ::RuntimeError
+    end
+
+
+    # This exception is raised if parsing failed.
+
+    class ParseError < VersionomyError
+    end
+
+
+    # This exception is raised if unparsing failed.
+
+    class UnparseError < VersionomyError
+    end
+
+
+    # This exception is raised if you try to set a value that is not
+    # allowed by the schema.
+
+    class IllegalValueError < VersionomyError
+    end
+
+
+    # This exception is raised if you try to perform a comparison
+    # between incompatible schemas.
+
+    class SchemaMismatchError < VersionomyError
+    end
+
+
+    # Base class for all Versionomy schema creation exceptions
+
+    class SchemaCreationError < VersionomyError
+    end
+
+
+    # This exception is raised during schema creation if you try to add
+    # the same symbol twice to the same symbolic field.
+
+    class SymbolRedefinedError < SchemaCreationError
+    end
+
+
+    # This exception is raised during schema creation if you try to
+    # create two fields covering overlapping ranges.
+
+    class RangeOverlapError < SchemaCreationError
+    end
+
+
+    # This exception is raised during schema creation if the range
+    # specification cannot be interpreted.
+
+    class RangeSpecificationError < SchemaCreationError
+    end
+
+
+    # This exception is raised during schema creation if you try to
+    # add a symbol to a non-symbolic schema.
+
+    class TypeMismatchError < SchemaCreationError
+    end
+
+
+    # This exception is raised during schema creation if you try to
+    # create a circular graph.
+
+    class CircularDescendantError < SchemaCreationError
+    end
+
+
+    # Base class for all Versionomy format creation exceptions.
+
+    class FormatCreationError < VersionomyError
+    end
+
+
+    # This exception is raised if you try to register a format
+    # with a name that has already been used.
+
+    class FormatRedefinedError < VersionomyError
+    end
+
+
+    # Raised by the Format registry if you try to retrieve a format with
+    # an unrecognized name in strict mode.
+
+    class UnknownFormatError < VersionomyError
+    end
+
+
+    # Raised when a conversion fails.
+
+    class ConversionError < VersionomyError
+    end
+
+
+    # Raised when a conversion fails because no conversion implementation
+    # was found.
+
+    class UnknownConversionError < ConversionError
+    end
+
+
+    # Raised when you try to register a conversion when one already
+    # exists for its schemas.
+
+    class ConversionRedefinedError < VersionomyError
+    end
+
+
+  end
+
+end
diff --git a/lib/versionomy/format.rb b/lib/versionomy/format.rb
new file mode 100644
index 0000000..70587cd
--- /dev/null
+++ b/lib/versionomy/format.rb
@@ -0,0 +1,231 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy format namespace
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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.
+# -----------------------------------------------------------------------------
+;
+
+
+require 'thread'
+require 'monitor'
+
+
+module Versionomy
+
+
+  # === Version number format.
+  #
+  # A format controls the parsing and unparsing of a version number.
+  # Any time a version number is parsed from a string, a format is provided
+  # to parse it. Similarly, every version number value references a format
+  # that is used to unparse it back into a string.
+  #
+  # A format is always tied to a particular schema and knows how to parse
+  # only that schema's version numbers.
+  #
+  # Under many circumstances, you should use the standard format, which
+  # can be retrieved by calling Versionomy::Format#standard. This format
+  # understands most common version numbers, including prerelease
+  # (e.g. alpha, beta, release candidate, etc.) forms and patchlevels.
+  #
+  # You may also create your own formats, either by implementing the
+  # format contract (see Versionomy::Format::Base), or by using the
+  # Versionomy::Format::Delimiter tool, which can be used to construct
+  # parsers for many version number formats.
+  #
+  # === Format registry
+  #
+  # Formats may be registered with Versionomy and given a name using the
+  # methods of this module. This allows version numbers to be serialized
+  # with their format. When a version number is serialized, its format
+  # name is written to the stream, along with the version number's string
+  # representation. When the version number is reconstructed, its format
+  # is looked up by name so versionomy can determine how to parse the
+  # string.
+  #
+  # Format names are strings that may include letters, numerals, dashes,
+  # underscores, and periods. By convention, periods are used as namespace
+  # delimiters. Format names without a namespace (that is, with no periods)
+  # are considered reserved for standard versionomy formats. If you define
+  # your own format, you should use a name that includes a namespace (e.g.
+  # "mycompany.LibraryVersion") to reduce the chance of name collisions.
+  #
+  # You may register formats directly using the register method, or set it
+  # up to be autoloaded on demand. When a format is requested, if it has
+  # not been registered explicitly, Versionomy looks for a format definition
+  # file for that format. Such a file has the name of the format, with the
+  # ".rb" extension for ruby (e.g. "mycompany.LibraryVersion.rb") and must
+  # be located in a directory in versionomy's format lookup path. By
+  # default, the directory containing versionomy's predefined formats
+  # (such as "standard") is in this path. However, you may add your own
+  # directories using the add_directory method. This lets you autoload your
+  # own formats. A format definition file itself must contain ruby code
+  # that defines the format and registers it using the correct name. See
+  # the files in the "lib/versionomy/format_definitions/" directory for
+  # examples.
+
+  module Format
+
+    @mutex = ::Mutex.new
+    @load_mutex = ::Monitor.new
+    @directories = [::File.expand_path(::File.dirname(__FILE__)+'/format_definitions')]
+    @names_to_formats = {}
+    @formats_to_names = {}
+
+    class << self
+
+
+      # Add a directory to the format path.
+      #
+      # The format path is an array of directory paths. These directories
+      # are searched for format definitions if a format name that has not
+      # been registered is requested.
+      #
+      # If high_priority_ is set to true, the directory is added to the
+      # front of the lookup path; otherwise it is added to the back.
+
+      def add_directory(path_, high_priority_=false)
+        path_ = ::File.expand_path(path_)
+        @mutex.synchronize do
+          unless @directories.include?(path_)
+            if high_priority_
+              @directories.unshift(path_)
+            else
+              @directories.push(path_)
+            end
+          end
+        end
+      end
+
+
+      # Get the format with the given name.
+      #
+      # If the given name has not been defined, attempts to autoload it from
+      # a format definition file. See the description of the Format module
+      # for details on this procedure.
+      #
+      # If the given name still cannot be resolved, and strict is set to
+      # true, raises Versionomy::Errors::UnknownFormatError. If strict is
+      # set to false, returns nil if the given name cannot be resolved.
+
+      def get(name_, strict_=false)
+        name_ = _check_name(name_)
+        format_ = @mutex.synchronize{ @names_to_formats[name_] }
+        if format_.nil?
+          # Attempt to find the format in the directory path
+          dirs_ = @mutex.synchronize{ @directories.dup }
+          dirs_.each do |dir_|
+            path_ = "#{dir_}/#{name_}.rb"
+            if ::File.readable?(path_)
+              @load_mutex.synchronize{ ::Kernel.load(path_) }
+            end
+            format_ = @mutex.synchronize{ @names_to_formats[name_] }
+            break unless format_.nil?
+          end
+        end
+        if format_.nil? && strict_
+          raise Errors::UnknownFormatError, name_
+        end
+        format_
+      end
+
+
+      # Determines whether a format with the given name has been registered
+      # explicitly. Does not attempt to autoload the format.
+
+      def registered?(name_)
+        name_ = _check_name(name_)
+        @mutex.synchronize{ @names_to_formats.include?(name_) }
+      end
+
+
+      # Register the given format under the given name.
+      #
+      # Valid names may contain only letters, digits, underscores, dashes,
+      # and periods.
+      #
+      # Raises Versionomy::Errors::FormatRedefinedError if the name has
+      # already been defined.
+
+      def register(name_, format_, silent_=false)
+        name_ = _check_name(name_)
+        @mutex.synchronize do
+          if @names_to_formats.include?(name_)
+            unless silent_
+              raise Errors::FormatRedefinedError, name_
+            end
+          else
+            @names_to_formats[name_] = format_
+            @formats_to_names[format_.object_id] = name_
+          end
+        end
+      end
+
+
+      # Get the canonical name for the given format, as a string.
+      # This is the first name the format was registered under.
+      #
+      # If the given format was never registered, and strict is set to true,
+      # raises Versionomy::Errors::UnknownFormatError. If strict is set to
+      # false, returns nil if the given format was never registered.
+
+      def canonical_name_for(format_, strict_=false)
+        name_ = @mutex.synchronize{ @formats_to_names[format_.object_id] }
+        if name_.nil? && strict_
+          raise Errors::UnknownFormatError
+        end
+        name_
+      end
+
+
+      private
+
+      def _check_name(name_)  # :nodoc:
+        name_ = name_.to_s
+        unless name_ =~ /\A[\w.-]+\z/
+          raise ::ArgumentError, "Illegal name: #{name_.inspect}"
+        end
+        name_
+      end
+
+
+    end
+
+  end
+
+
+  # Versionomy::Formats is an alias for Versionomy::Format, for backward
+  # compatibility with version 0.1.0 code. It is deprecated; use
+  # Versionomy::Format instead.
+  Formats = Format
+
+
+end
diff --git a/lib/versionomy/format/base.rb b/lib/versionomy/format/base.rb
new file mode 100644
index 0000000..763356f
--- /dev/null
+++ b/lib/versionomy/format/base.rb
@@ -0,0 +1,145 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy format base class
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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
+
+
+    # The base format.
+    #
+    # This format doesn't actually do anything useful. It causes all strings
+    # to parse to the schema's default value, and unparses all values to the
+    # empty string. Instead, the purpose here is to define the API for a
+    # format.
+    #
+    # All formats must define the methods +schema+, +parse+, and +unparse+.
+    # It is also recommended that formats define the <tt>===</tt> method,
+    # though this is not strictly required. Finally, formats may optionally
+    # implement <tt>uparse_for_serialize</tt>.
+    #
+    # Formats need not extend this base class, as long as they duck-type
+    # these methods.
+
+    class Base
+
+
+      # Create an instance of this base format, with the given schema.
+
+      def initialize(schema_)
+        @_schema = schema_
+      end
+
+
+      def inspect   # :nodoc:
+        "#<#{self.class}:0x#{object_id.to_s(16)} schema=#{@_schema.inspect}>"
+      end
+
+      def to_s   # :nodoc:
+        inspect
+      end
+
+
+      # Returns the schema understood by this format.
+
+      def schema
+        @_schema
+      end
+
+
+      # Parse the given string and return a value.
+      #
+      # The optional parameter hash can be used to pass parameters to the
+      # parser to affect its behavior. The exact parameters supported are
+      # defined by the format.
+
+      def parse(string_, params_=nil)
+        Value.new([], self)
+      end
+
+
+      # Unparse the given value and return a string.
+      #
+      # The optional parameter hash can be used to pass parameters to the
+      # unparser to affect its behavior. The exact parameters supported
+      # are defined by the format.
+
+      def unparse(value_, params_=nil)
+        ''
+      end
+
+
+      # An optional method that does unparsing especially for serialization.
+      # Implement this if normal unparsing is "lossy" and doesn't guarantee
+      # reconstruction of the version number. This method should attempt to
+      # unparse in such a way that the entire version value can be
+      # reconstructed from the unparsed string. Serialization routines will
+      # first attempt to call this method to unparse for serialization. If
+      # this method is not present, the normal unparse method will be used.
+      #
+      # Return either the unparsed string, or an array consisting of the
+      # unparsed string and a hash of parse params to pass to the parser
+      # when the string is to be reconstructed. You may also either return
+      # nil or raise Versionomy::Errors::UnparseError if the unparsing
+      # cannot be done satisfactorily for serialization. In this case,
+      # serialization will be done using the raw value data rather than an
+      # unparsed string.
+      #
+      # This default implementation just turns around and calls unparse.
+      # Thus it is equivalent to the method not being present at all.
+
+      def unparse_for_serialization(value_)
+        unparse(value_)
+      end
+
+
+      # Determine whether the given value uses this format.
+
+      def ===(obj_)
+        if obj_.kind_of?(Value)
+          obj_.format == self
+        else
+          obj_ == self
+        end
+      end
+
+
+    end
+
+
+  end
+
+end
diff --git a/lib/versionomy/format/delimiter.rb b/lib/versionomy/format/delimiter.rb
new file mode 100644
index 0000000..ca10b4e
--- /dev/null
+++ b/lib/versionomy/format/delimiter.rb
@@ -0,0 +1,1047 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy delimiter format
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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
+
+
+    # The Delimiter format class provides a DSL for building formats that
+    # can handle most cases where the fields of a version number appear
+    # consecutively in order in the string formatting. We expect most
+    # version number schemes should fall into this category.
+    #
+    # In general, the strategy is to provide, for each field, a set of
+    # regular expressions that recognize different formats for that field.
+    # Every field must be of the form "(pre)(value)(post)"
+    # where (pre) and (post) are delimiters preceding and
+    # following the value. Either or both delimiters may be the empty string.
+    #
+    # To parse a string, the string is scanned from left to right and
+    # matched against the format for the fields in order. If the string
+    # matches, that part of the string is consumed and the field value is
+    # interpreted from it. If the string does not match, and the field is
+    # not marked as "required", then the field is set to its default value
+    # and the next field is tried.
+    #
+    # During parsing, the actual delimiters, along with other information
+    # such as whether or not fields are required, are saved into a default
+    # set of parameters for unparsing. These are saved in the unparse_params
+    # of the version value, so that the version number can be unparsed in
+    # generally the same form. If the version number value is modified, this
+    # allows the unparsing of the new value to generally follow the format
+    # of the original string.
+    #
+    # Formats that use the Delimiter mechanism also provide support for
+    # certain parsing and unparsing parameters. See the documentation for
+    # the parse and unparse methods for details.
+    #
+    # For a usage example, see the definition of the standard format in
+    # Versionomy::Format::Standard#create.
+
+    class Delimiter < Base
+
+
+      # Create a format using delimiter tools.
+      # You should provide the version number schema, a set of default
+      # options, and a block.
+      #
+      # Within the block, you can call methods of
+      # Versionomy::Format::Delimiter::Builder
+      # to provide parsers for the fields of the schema. Any fields you do
+      # not explicitly configure will get parsed in a default manner.
+
+      def initialize(schema_, default_opts_={}, &block_)
+        # Special case used by modified_copy
+        if schema_.kind_of?(Delimiter)
+          orig_ = schema_
+          @schema = orig_.schema
+          @default_parse_params = orig_.default_parse_params
+          @default_unparse_params = orig_.default_unparse_params
+          @field_handlers = orig_.instance_variable_get(:@field_handlers).dup
+          builder_ = Delimiter::Builder.new(@schema, @field_handlers,
+            @default_parse_params, @default_unparse_params)
+          ::Blockenspiel.invoke(block_, builder_)
+          return
+        end
+
+        @schema = schema_
+        @field_handlers = {}
+        @default_parse_params = {}
+        @default_unparse_params = {}
+        builder_ = Delimiter::Builder.new(@schema, @field_handlers,
+          @default_parse_params, @default_unparse_params)
+        ::Blockenspiel.invoke(block_, builder_)
+        _interpret_field_lists(@default_unparse_params)
+        @schema.names.each do |name_|
+          @field_handlers[name_] ||= Delimiter::FieldHandler.new(@schema.field_named(name_), default_opts_)
+        end
+      end
+
+
+      # Returns the schema understood by this format.
+      # This method is required by the Format contract.
+
+      def schema
+        @schema
+      end
+
+
+      # Parse the given string and return a value.
+      # This method is required by the Format contract.
+      #
+      # This method provides, out of the box, support for the following
+      # parse parameters:
+      #
+      # <tt>:extra_characters</tt>::
+      #   Determines what to do if the entire string cannot be consumed by
+      #   the parsing process. If set to <tt>:ignore</tt>, any extra
+      #   characters are ignored. If set to <tt>:suffix</tt>, the extra
+      #   characters are set as the <tt>:suffix</tt> unparse parameter and
+      #   are thus appended to the end of the string when unparsing takes
+      #   place. If set to <tt>:error</tt> (the default), causes a
+      #   Versionomy::Errors::ParseError to be raised if there are
+      #   uninterpreted characters.
+
+      def parse(string_, params_=nil)
+        parse_params_ = default_parse_params
+        parse_params_.merge!(params_) if params_
+        parse_state_ = {
+          :backtrack => nil,
+          :string => string_,
+          :values => {},
+          :unparse_params => {},
+          :field => @schema.root_field,
+          :recognizer_index => 0,
+          :previous_field_missing => false
+        }
+        while (field_ = parse_state_[:field])
+          handler_ = @field_handlers[field_.name]
+          recognizer_ = handler_.get_recognizer(parse_state_[:recognizer_index])
+          parse_data_ = nil
+          if recognizer_
+            parse_state_[:recognizer_index] += 1
+            parse_data_ = recognizer_.parse(parse_state_, parse_params_)
+            if parse_data_
+              parse_state_[:previous_field_missing] = false
+              if recognizer_.requires_next_field
+                parse_state_ = {
+                  :backtrack => parse_state_,
+                  :string => parse_state_[:string],
+                  :values => parse_state_[:values].dup,
+                  :unparse_params => parse_state_[:unparse_params].dup,
+                  :field => parse_state_[:field],
+                  :recognizer_index => 0,
+                  :previous_field_missing => false,
+                  :next_field_required => true,
+                }
+              else
+                parse_state_[:next_field_required] = false
+              end
+            end
+          elsif parse_state_[:next_field_required]
+            parse_state_ = parse_state_[:backtrack]
+          else
+            parse_data_ = [handler_.default_value, nil, nil, nil]
+            parse_state_[:previous_field_missing] = true
+            parse_state_[:next_field_required] = false
+          end
+          if parse_data_
+            parse_state_[:values][field_.name] = parse_data_[0]
+            parse_state_[:string] = parse_data_[2] if parse_data_[2]
+            parse_state_[:unparse_params].merge!(parse_data_[3]) if parse_data_[3]
+            parse_state_[:field] = field_.child(parse_data_[0])
+            parse_state_[:recognizer_index] = 0
+            handler_.set_style_unparse_param(parse_data_[1], parse_state_[:unparse_params])
+          end
+        end
+        unparse_params_ = parse_state_[:unparse_params]
+        if parse_state_[:string].length > 0
+          case parse_params_[:extra_characters]
+          when :ignore
+            # do nothing
+          when :suffix
+            unparse_params_[:suffix] = parse_state_[:string]
+          else
+            raise Errors::ParseError, "Extra characters: #{parse_state_[:string].inspect}"
+          end
+        end
+        Value.new(parse_state_[:values], self, unparse_params_)
+      end
+
+
+      # Unparse the given value and return a string.
+      # This method is required by the Format contract.
+      #
+      # This method provides, out of the box, support for the following
+      # unparse parameters:
+      #
+      # <tt>:suffix</tt>::
+      #   A string to append to the unparsed string. Default is nothing.
+      # <tt>:required_fields</tt>::
+      #   An array of field names that must be present in the unparsed
+      #   string. These are generally fields with default_value_optional
+      #   set, but that we want present in the string anyway. For example,
+      #   in the version number "2.0.0", often the third field will be
+      #   default_value_optional, but we can include it in the required
+      #   fields passed to unparse to force it to appear in the string.
+      # <tt>:optional_fields</tt>::
+      #   An array of field names that should have their presence in
+      #   required_fields undone.
+      # <tt>:<i>fieldname</i>_required</tt>::
+      #   This is an alternate way of specifying whether a potentially
+      #   optional field should be required. Accepted values are true
+      #   and false.
+      # <tt>:<i>fieldname</i>_style</tt>::
+      #   Specify the style for unparsing the given field. See
+      #   Versionomy::Format::Delimiter::Builder#field for more
+      #   discussion of styles.
+      # <tt>:<i>fieldname</i>_delim</tt>::
+      #   Set the pre-delimiter for the given field, if supported.
+      #   Note that the string specified must be legal-- it must match the
+      #   regexp for the field. If not, it will revert to the default.
+      # <tt>:<i>fieldname</i>_postdelim</tt>::
+      #   Set the post-delimiter for the given field, if supported.
+      #   Note that the string specified must be legal-- it must match the
+      #   regexp for the field. If not, it will revert to the default.
+      # <tt>:<i>fieldname</i>_case</tt>::
+      #   This is used by letter-formatted integer fields only, and
+      #   sets the case to use while unparsing. Recognized values are
+      #   <tt>:lower</tt> (the default), and <tt>:upper</tt>.
+
+      def unparse(value_, params_=nil)
+        unparse_params_ = value_.unparse_params || default_unparse_params
+        _interpret_field_lists(unparse_params_)
+        if params_
+          unparse_params_.merge!(params_)
+          _interpret_field_lists(unparse_params_)
+        end
+        skipped_handler_list_ = nil
+        requires_next_field_ = false
+        string_ = ''
+        value_.each_field_object do |field_, val_|
+          handler_ = @field_handlers[field_.name]
+          unparse_data_ = handler_.unparse(val_, unparse_params_, requires_next_field_)
+          if unparse_data_
+            if skipped_handler_list_ && handler_.requires_previous_field
+              skipped_handler_list_.each do |pair_|
+                frag_ = pair_[0].unparse(pair_[1], unparse_params_, true)
+                unless frag_
+                  raise Errors::UnparseError, "Field #{field_.name} empty although a prerequisite for a later field"
+                end
+                string_ << frag_[0]
+              end
+            end
+            skipped_handler_list_ = nil
+            string_ << unparse_data_[0]
+            requires_next_field_ = unparse_data_[1]
+          else
+            if handler_.requires_previous_field
+              (skipped_handler_list_ ||= []) << [handler_, val_]
+            else
+              skipped_handler_list_ = [[handler_, val_]]
+            end
+            requires_next_field_ = false
+          end
+        end
+        string_ << (unparse_params_[:suffix] || '')
+        string_
+      end
+
+
+      # Return a copy of the default parsing parameters used by this format.
+      # This hash cannot be edited in place. To modify the default parsing
+      # parameters, use modified_copy and call
+      # Versionomy::Format::Delimiter::Builder#default_parse_params in the block.
+
+      def default_parse_params
+        @default_parse_params.dup
+      end
+
+
+      # Return a copy of the default unparsing parameters used by this format.
+      # This hash cannot be edited in place. To modify the default unparsing
+      # parameters, use modified_copy and call
+      # Versionomy::Format::Delimiter::Builder#default_unparse_params in the block.
+
+      def default_unparse_params
+        @default_unparse_params.dup
+      end
+
+
+      # Create a copy of this format, with the modifications given in the
+      # provided block. You can call methods of Versionomy::Format::Delimiter::Builder
+      # in the block. Field handlers that you specify in that block will
+      # override and change the field handlers from the original. Any fields
+      # not specified in this block will use the handlers from the original.
+
+      def modified_copy(&block_)
+        Delimiter.new(self, &block_)
+      end
+
+
+      # A utility method that interprets required_fields and
+      # optional_fields parameters.
+
+      def _interpret_field_lists(unparse_params_)  # :nodoc:
+        fields_ = unparse_params_.delete(:required_fields)
+        if fields_
+          fields_ = [fields_] unless fields_.kind_of?(::Array)
+          fields_.each do |f_|
+            unparse_params_["#{f_}_required".to_sym] = true
+          end
+        end
+        fields_ = unparse_params_.delete(:optional_fields)
+        if fields_
+          fields_ = [fields_] unless fields_.kind_of?(::Array)
+          fields_.each do |f_|
+            unparse_params_["#{f_}_required".to_sym] = false
+          end
+        end
+      end
+      private :_interpret_field_lists
+
+
+      # This class defines methods that you can call within the DSL block
+      # passed to Versionomy::Format::Delimiter#new.
+      #
+      # Generally, you call the field method of this class a number of times
+      # to define the formatting for each field.
+
+      class Builder
+
+        include ::Blockenspiel::DSL
+
+        def initialize(schema_, field_handlers_, default_parse_params_, default_unparse_params_)  # :nodoc:
+          @schema = schema_
+          @field_handlers = field_handlers_
+          @default_parse_params = default_parse_params_
+          @default_unparse_params = default_unparse_params_
+        end
+
+
+        # Specify how to handle a given field.
+        # You must pass the name of the field, a hash of options, and a
+        # block defining the handling of the field.
+        #
+        # Within the block, you set up "recognizers" for various regular
+        # expression patterns. These recognizers are tested in order when
+        # parsing a version number.
+        #
+        # The methods that can be called from the block are determined by
+        # the type of field. If the field is an integer field, the methods
+        # of Versionomy::Format::Delimiter::IntegerFieldBuilder can be
+        # called from the block. If the field is a string field, the methods
+        # of Versionomy::Format::Delimiter::StringFieldBuilder can be
+        # called. If the field is a symbolic field, the methods of
+        # Versionomy::Format::Delimiter::SymbolFieldBuilder can be called.
+        #
+        # === Options
+        #
+        # The opts hash includes a number of options that control how the
+        # field is parsed.
+        #
+        # Some of these are regular expressions that indicate what patterns
+        # are recognized by the parser. Regular expressions should be passed
+        # in as the string representation of the regular expression, not a
+        # Regexp object itself. For example, use the string '-' rather than
+        # the Regexp /-/ to recognize a hyphen delimiter.
+        #
+        # The following options are recognized:
+        #
+        # <tt>:default_value_optional</tt>::
+        #   If set to true, this the field may be omitted in the unparsed
+        #   (formatted) version number, if the value is the default value
+        #   for this field. However, if the following field is present and
+        #   set as <tt>:requires_previous_field</tt>, then this field is
+        #   still unparsed even if it is its default value.
+        #   For example, for a version number like "2.0.0", often the third
+        #   field is optional, but the first and second are required, so it
+        #   will often be unparsed as "2.0".
+        #   Default is false.
+        # <tt>:default_value</tt>::
+        #   The actual value set for this field if it is omitted from the
+        #   version string. Defaults to the field's schema default value,
+        #   but that can be overridden here.
+        # <tt>:case_sensitive</tt>::
+        #   If set to true, the regexps are case-sensitive. Default is false.
+        # <tt>:delimiter_regexp</tt>::
+        #   The regular expression string for the pre-delimiter. This pattern
+        #   must appear before the current value in the string, and is
+        #   consumed when the field is parsed, but is not part of the value
+        #   itself. Default is '\.' to recognize a period.
+        # <tt>:post_delimiter_regexp</tt>::
+        #   The regular expression string for the post-delimiter. This pattern
+        #   must appear before the current value in the string, and is
+        #   consumed when the field is parsed, but is not part of the value
+        #   itself. Default is '' to indicate no post-delimiter.
+        # <tt>:expected_follower_regexp</tt>::
+        #   The regular expression string for what characters are expected to
+        #   follow this field in the string. These characters are not part
+        #   of the field itself, and are *not* consumed when the field is
+        #   parsed; however, they must be present immediately after this
+        #   field in order for the field to be recognized. Default is '' to
+        #   indicate that we aren't testing for any particular characters.
+        # <tt>:default_delimiter</tt>::
+        #   The default delimiter string. This is the string that is used
+        #   to unparse a field value if the field was not present when the
+        #   value was originally parsed. For example, if you parse the string
+        #   "2.0", bump the tiny version so that the value is "2.0.1", and
+        #   unparse, the unparsing won't receive the second period from
+        #   parsing the original string, so its delimiter will use the default.
+        #   Default value is '.'
+        # <tt>:default_post_delimiter</tt>::
+        #   The default post-delimiter string. Default value is '' indicating
+        #   no post-delimiter.
+        # <tt>:requires_previous_field</tt>::
+        #   If set to true, this field's presence in a formatted version string
+        #   requires the presence of the previous field. For example, in a
+        #   typical version number "major.minor.tiny", tiny should appear in
+        #   the string only if minor also appears, so tiny should have this
+        #   parameter set to true. The default is true, so you must specify
+        #   <tt>:requires_previous_field => false</tt> explicitly if you want
+        #   a field not to require the previous field.
+        # <tt>:requires_next_field</tt>::
+        #   If set to true, this field's presence in a formatted version
+        #   string requires the presence of the next field. For example, in
+        #   the version "1.0a5", the release_type field requires the presence
+        #   of the alpha_version field, because if the "5" was missing, the
+        #   string "1.0a" looks like a patchlevel indicator. Often it is
+        #   easier to set default_value_optional in the next field, but this
+        #   option is also available if the behavior is dependent on the
+        #   value of this previous field.
+        # <tt>:default_style</tt>::
+        #   The default style for this field. This is the style used for
+        #   unparsing if the value was not constructed by a parser or is
+        #   otherwise missing the style for this field.
+        #
+        # === Styles
+        #
+        # A field may have different representation "styles". For example,
+        # you could represent a patchlevel of 1 as "1.0-1" or "1.0a".
+        # When a version number string is parsed, the parser and unparser
+        # work together to remember which style was parsed, and that style
+        # is used when the version number is unparsed.
+        #
+        # Specify styles as options to the calls made within the block that
+        # is passed to this method. In the above case, you could define the
+        # patchlevel field with a block that has two calls, one that uses
+        # Delimiter::IntegerFieldBuilder#recognize_number and passes the
+        # option <tt>:style => :number</tt>, and another that uses
+        # Delimiter::IntegerFieldBuilder#recognize_letter and passes the
+        # option <tt>:style => :letter</tt>.
+        #
+        # The standard format uses styles to preserve the different
+        # syntaxes for the release_type field. See the source code in
+        # Versionomy::Format::Standard#create for this example.
+
+        def field(name_, opts_={}, &block_)
+          name_ = name_.to_sym
+          field_ = @schema.field_named(name_)
+          if !field_
+            raise Errors::FormatCreationError, "Unknown field name #{name_.inspect}"
+          end
+          @field_handlers[name_] = Delimiter::FieldHandler.new(field_, opts_, &block_)
+        end
+
+
+        # Set or modify the default parameters used when parsing a value.
+
+        def default_parse_params(params_)
+          @default_parse_params.merge!(params_)
+        end
+
+
+        # Set or modify the default parameters used when unparsing a value.
+
+        def default_unparse_params(params_)
+          @default_unparse_params.merge!(params_)
+        end
+
+      end
+
+
+      # This class defines methods that can be called from the block passed
+      # to Versionomy::Format::Delimiter::Builder#field if the field is
+      # of integer type.
+
+      class IntegerFieldBuilder
+
+        include ::Blockenspiel::DSL
+
+        def initialize(recognizers_, field_, default_opts_)  # :nodoc:
+          @recognizers = recognizers_
+          @field = field_
+          @default_opts = default_opts_
+        end
+
+
+        # Recognize a numeric-formatted integer field.
+        # Using the opts parameter, you can override any of the field's
+        # overall parsing options. You may also set the following additional
+        # options:
+        #
+        # <tt>:strip_leading_zeros</tt>::
+        #   If false (the default), and a value has leading zeros, it is
+        #   assumed that the field has a minimum width, and unparsing will
+        #   always pad left with zeros to reach that minimum width. If set
+        #   to true, leading zeros are stripped from a value, and this
+        #   padding is never done.
+
+        def recognize_number(opts_={})
+          @recognizers << Delimiter::BasicIntegerRecognizer.new(@field, @default_opts.merge(opts_))
+        end
+
+
+        # Recognize a letter-formatted integer field. That is, the value is
+        # formatted as an alphabetic letter where "a" represents 1, up to
+        # "z" representing 26.
+        #
+        # Using the opts parameter, you can override any of the field's
+        # overall parsing options. You may also set the following additional
+        # options:
+        #
+        # <tt>:case</tt>::
+        #   Case-sensitivity of the letter. Possible values are
+        #   <tt>:upper</tt>, <tt>:lower</tt>, and <tt>:either</tt>.
+        #   Default is <tt>:either</tt>.
+
+        def recognize_letter(opts_={})
+          @recognizers << Delimiter::AlphabeticIntegerRecognizer.new(@field, @default_opts.merge(opts_))
+        end
+
+      end
+
+
+      # This class defines methods that can be called from the block passed
+      # to Versionomy::Format::Delimiter::Builder#field if the field is
+      # of string type.
+
+      class StringFieldBuilder
+
+        include ::Blockenspiel::DSL
+
+        def initialize(recognizers_, field_, default_opts_)  # :nodoc:
+          @recognizers = recognizers_
+          @field = field_
+          @default_opts = default_opts_
+        end
+
+
+        # Recognize a string field whose value matches a regular expression.
+        # The regular expression must be passed as a string. E.g. use
+        # "[a-z]+" instead of /[a-z]+/.
+        # Using the opts parameter, you can override any of the field's
+        # overall parsing options.
+
+        def recognize_regexp(regexp_, opts_={})
+          @recognizers << Delimiter::RegexpStringRecognizer.new(@field, regexp_, @default_opts.merge(opts_))
+        end
+
+      end
+
+
+      # This class defines methods that can be called from the block passed
+      # to Versionomy::Format::Delimiter::Builder#field if the field is
+      # of symbolic type.
+
+      class SymbolFieldBuilder
+
+        include ::Blockenspiel::DSL
+
+        def initialize(recognizers_, field_, default_opts_)  # :nodoc:
+          @recognizers = recognizers_
+          @field = field_
+          @default_opts = default_opts_
+        end
+
+
+        # Recognize a symbolic value represented by a particular regular
+        # expression. The regular expression must be passed as a string.
+        # E.g. use "[a-z]+" instead of /[a-z]+/.
+        # The "canonical" parameter indicates the canonical syntax for the
+        # value, for use in unparsing.
+        #
+        # Using the opts parameter, you can override any of the field's
+        # overall parsing options.
+
+        def recognize_regexp(value_, regexp_, canonical_, opts_={}, &block_)
+          @recognizers << Delimiter::RegexpSymbolRecognizer.new(@field, value_, regexp_, canonical_, @default_opts.merge(opts_))
+        end
+
+
+        # Recognize a set of symbolic values, each represented by a
+        # particular regular expression, but all sharing the same delimiters
+        # and options. Use this instead of repeated calls to recognize_regexp
+        # for better performance.
+        #
+        # Using the opts parameter, you can override any of the field's
+        # overall parsing options.
+        #
+        # In the block, you should call methods of
+        # Versionomy::Format::Delimiter::MappingSymbolBuilder to map values
+        # to regular expression representations.
+
+        def recognize_regexp_map(opts_={}, &block_)
+          @recognizers << Delimiter::MappingSymbolRecognizer.new(@field, @default_opts.merge(opts_), &block_)
+        end
+
+      end
+
+
+      # Methods in this class can be called from the block passed to
+      # Versionomy::Format::Delimiter::SymbolFieldBuilder#recognize_regexp_map
+      # to define the mapping between the values of a symbolic field and
+      # the string representations of those values.
+
+      class MappingSymbolBuilder
+
+        include ::Blockenspiel::DSL
+
+        def initialize(mappings_in_order_, mappings_by_value_)  # :nodoc:
+          @mappings_in_order = mappings_in_order_
+          @mappings_by_value = mappings_by_value_
+        end
+
+
+        # Map a value to a string representation.
+        # The optional regexp field, if specified, provides a regular
+        # expression pattern for matching the value representation. If it
+        # is omitted, the representation is used as the regexp.
+
+        def map(value_, representation_, regexp_=nil)
+          regexp_ ||= representation_
+          array_ = [regexp_, representation_, value_]
+          @mappings_by_value[value_] ||= array_
+          @mappings_in_order << array_
+        end
+
+      end
+
+
+      # This class handles the parsing and unparsing of a single field.
+      # It manages an ordered list of recognizers, each understanding a
+      # particular syntax. These recognizers are checked in order when
+      # parsing and unparsing.
+
+      class FieldHandler  # :nodoc:
+
+
+        # Creates a FieldHandler, using a DSL block appropriate to the
+        # field type to configure the recognizers.
+
+        def initialize(field_, default_opts_={}, &block_)
+          @field = field_
+          @recognizers = []
+          @requires_previous_field = default_opts_.fetch(:requires_previous_field, true)
+          @default_value = default_opts_[:default_value] || field_.default_value
+          @default_style = default_opts_.fetch(:default_style, nil)
+          @style_unparse_param_key = "#{field_.name}_style".to_sym
+          if block_
+            builder_ = case field_.type
+              when :integer
+                Delimiter::IntegerFieldBuilder.new(@recognizers, field_, default_opts_)
+              when :string
+                Delimiter::StringFieldBuilder.new(@recognizers, field_, default_opts_)
+              when :symbol
+                Delimiter::SymbolFieldBuilder.new(@recognizers, field_, default_opts_)
+            end
+            ::Blockenspiel.invoke(block_, builder_)
+          end
+        end
+
+
+        # Returns true if this field can appear in an unparsed string only
+        # if the previous field also appears.
+
+        def requires_previous_field
+          @requires_previous_field
+        end
+
+
+        # Returns the default value set when this field is missing from a
+        # version string.
+
+        def default_value
+          @default_value
+        end
+
+
+        # Gets the given indexed recognizer. Returns nil if the index is out
+        # of range.
+
+        def get_recognizer(index_)
+          @recognizers[index_]
+        end
+
+
+        # Finishes up parsing by setting the appropriate style field in the
+        # unparse_params, if needed.
+
+        def set_style_unparse_param(style_, unparse_params_)
+          if style_ && style_ != @default_style
+            unparse_params_[@style_unparse_param_key] = style_
+          end
+        end
+
+
+        # Unparse a string from this field value.
+        # This may return nil if this field is not required.
+
+        def unparse(value_, unparse_params_, required_for_later_)
+          style_ = unparse_params_[@style_unparse_param_key] || @default_style
+          @recognizers.each do |recog_|
+            if recog_.should_unparse?(value_, style_)
+              fragment_ = recog_.unparse(value_, style_, unparse_params_, required_for_later_)
+              return fragment_ ? [fragment_, recog_.requires_next_field] : nil
+            end
+          end
+          required_for_later_ ? ['', false] : nil
+        end
+
+      end
+
+
+      # A recognizer handles both parsing and unparsing of a particular kind
+      # of syntax. During parsing, it recognizes the syntax based on regular
+      # expressions for the delimiters and the value. If the string matches
+      # the syntax recognized by this object, an appropriate value and style
+      # are returned. During unparsing, the should_unparse? method should be
+      # called first to determine whether this object is responsible for
+      # unparsing the given value and style. If should_unparse? returns
+      # true, the unparse method should be called to actually generate a
+      # a string fragment, or return nil if the field is determined to be
+      # optional in the unparsed string.
+      #
+      # This is a base class. The actual classes should implement
+      # initialize, parsed_value, and unparsed_value, and may optionally
+      # override the should_unparse? method.
+
+      class RecognizerBase  # :nodoc:
+
+        # Derived classes should call this from their initialize method
+        # to set up the recognizer's basic parameters.
+
+        def setup(field_, value_regexp_, opts_)
+          @style = opts_[:style]
+          @default_value_optional = opts_[:default_value_optional]
+          @default_value = opts_[:default_value] || field_.default_value
+          @regexp_options = opts_[:case_sensitive] ? nil : ::Regexp::IGNORECASE
+          @value_regexp = ::Regexp.new("\\A(#{value_regexp_})", @regexp_options)
+          regexp_ = opts_[:delimiter_regexp] || '\.'
+          @delimiter_regexp = regexp_.length > 0 ? ::Regexp.new("\\A(#{regexp_})", @regexp_options) : nil
+          @full_delimiter_regexp = regexp_.length > 0 ? ::Regexp.new("\\A(#{regexp_})\\z", @regexp_options) : nil
+          regexp_ = opts_[:post_delimiter_regexp] || ''
+          @post_delimiter_regexp = regexp_.length > 0 ? ::Regexp.new("\\A(#{regexp_})", @regexp_options) : nil
+          @full_post_delimiter_regexp = regexp_.length > 0 ? ::Regexp.new("\\A(#{regexp_})\\z", @regexp_options) : nil
+          regexp_ = opts_[:expected_follower_regexp] || ''
+          @follower_regexp = regexp_.length > 0 ? ::Regexp.new("\\A(#{regexp_})", @regexp_options) : nil
+          @default_delimiter = opts_[:default_delimiter] || '.'
+          @default_post_delimiter = opts_[:default_post_delimiter] || ''
+          @requires_previous_field = opts_.fetch(:requires_previous_field, true)
+          @requires_next_field = opts_.fetch(:requires_next_field, false)
+          name_ = field_.name
+          @delim_unparse_param_key = "#{name_}_delim".to_sym
+          @post_delim_unparse_param_key = "#{name_}_postdelim".to_sym
+          @required_unparse_param_key = "#{name_}_required".to_sym
+        end
+
+
+        # Attempt to parse the field from the string if the syntax matches
+        # this recognizer's configuration.
+        # Returns either nil, indicating that this recognizer doesn't match
+        # the given syntax, or a two element array of the value and style.
+
+        def parse(parse_state_, parse_params_)
+          return nil if @requires_previous_field && parse_state_[:previous_field_missing]
+          string_ = parse_state_[:string]
+          if @delimiter_regexp
+            match_ = @delimiter_regexp.match(string_)
+            return nil unless match_
+            delim_ = match_[0]
+            string_ = match_.post_match
+          else
+            delim_ = ''
+          end
+          match_ = @value_regexp.match(string_)
+          return nil unless match_
+          value_ = match_[0]
+          string_ = match_.post_match
+          if @post_delimiter_regexp
+            match_ = @post_delimiter_regexp.match(string_)
+            return nil unless match_
+            post_delim_ = match_[0]
+            string_ = match_.post_match
+          else
+            post_delim_ = nil
+          end
+          if @follower_regexp
+            match_ = @follower_regexp.match(string_)
+            return nil unless match_
+          end
+          parse_result_ = parsed_value(value_, parse_params_)
+          return nil unless parse_result_
+          unparse_params_ = parse_result_[1] || {}
+          if delim_ != @default_delimiter
+            unparse_params_[@delim_unparse_param_key] = delim_
+          end
+          if post_delim_ && post_delim_ != @default_post_delimiter
+            unparse_params_[@post_delim_unparse_param_key] = post_delim_
+          end
+          unparse_params_[@required_unparse_param_key] = true if @default_value_optional
+          [parse_result_[0], @style, string_, unparse_params_]
+        end
+
+
+        # Returns true if this field can appear in an unparsed string only
+        # if the next field also appears.
+
+        def requires_next_field
+          @requires_next_field
+        end
+
+
+        # Returns true if this recognizer should be used to unparse the
+        # given value and style.
+
+        def should_unparse?(value_, style_)
+          style_ == @style
+        end
+
+
+        # Unparse the given value in the given style, and return a string
+        # fragment, or nil if the field is determined to be "optional" to
+        # unparse and isn't otherwise required (because a later field needs
+        # it to be present, for example).
+        #
+        # It is guaranteed that this will be called only if should_unparse?
+        # returns true.
+
+        def unparse(value_, style_, unparse_params_, required_for_later_)
+          str_ = nil
+          if !@default_value_optional || value_ != @default_value ||
+              required_for_later_ || unparse_params_[@required_unparse_param_key]
+          then
+            str_ = unparsed_value(value_, style_, unparse_params_)
+            if str_
+              if !@full_delimiter_regexp
+                delim_ = ''
+              else
+                delim_ = unparse_params_[@delim_unparse_param_key] || @default_delimiter
+                if @full_delimiter_regexp !~ delim_
+                  delim_ = @default_delimiter
+                end
+              end
+              if !@full_post_delimiter_regexp
+                post_delim_ = ''
+              else
+                post_delim_ = unparse_params_[@post_delim_unparse_param_key] || @default_post_delimiter
+                if @full_post_delimiter_regexp !~ post_delim_
+                  post_delim_ = @default_post_delimiter
+                end
+              end
+              str_ = delim_ + str_ + post_delim_
+            end
+            str_
+          else
+            nil
+          end
+        end
+
+      end
+
+
+      # A recognizer for a numeric integer field
+
+      class BasicIntegerRecognizer < RecognizerBase  #:nodoc:
+
+        def initialize(field_, opts_={})
+          @strip_leading_zeros = opts_[:strip_leading_zeros]
+          @width_unparse_param_key = "#{field_.name}_width".to_sym
+          setup(field_, '\d+', opts_)
+        end
+
+        def parsed_value(value_, parse_params_)
+          if !@strip_leading_zeros && value_ =~ /^0\d/
+            [value_.to_i, {@width_unparse_param_key => value_.length}]
+          else
+            [value_.to_i, nil]
+          end
+        end
+
+        def unparsed_value(value_, style_, unparse_params_)
+          if !@strip_leading_zeros && (width_ = unparse_params_[@width_unparse_param_key])
+            "%0#{width_.to_i}d" % value_
+          else
+            value_.to_s
+          end
+        end
+
+      end
+
+
+      # A recognizer for an alphabetic integer field. Such a field
+      # represents values 1-26 as letters of the English alphabet.
+
+      class AlphabeticIntegerRecognizer < RecognizerBase  # :nodoc:
+
+        def initialize(field_, opts_={})
+          @case_unparse_param_key = "#{field_.name}_case".to_sym
+          @case = opts_[:case]
+          case @case
+          when :upper
+            value_regexp_ = '[A-Z]'
+          when :lower
+            value_regexp_ = '[a-z]'
+          else #either
+            value_regexp_ = '[a-zA-Z]'
+          end
+          setup(field_, value_regexp_, opts_)
+        end
+
+        def parsed_value(value_, parse_params_)
+          value_ = value_.unpack('c')[0]  # Compatible with both 1.8 and 1.9
+          if value_ >= 97 && value_ <= 122
+            [value_ - 96, {@case_unparse_param_key => :lower}]
+          elsif value_ >= 65 && value_ <= 90
+            [value_ - 64, {@case_unparse_param_key => :upper}]
+          else
+            [0, nil]
+          end
+        end
+
+        def unparsed_value(value_, style_, unparse_params_)
+          if value_ >= 1 && value_ <= 26
+            if unparse_params_[@case_unparse_param_key] == :upper
+              (value_+64).chr
+            else
+              (value_+96).chr
+            end
+          else
+            value_.to_s
+          end
+        end
+
+      end
+
+
+      # A recognizer for strings that match a particular given regular
+      # expression, for use in string-valued fields.
+
+      class RegexpStringRecognizer < RecognizerBase  # :nodoc:
+
+        def initialize(field_, regexp_='[a-zA-Z0-9]+', opts_={})
+          setup(field_, regexp_, opts_)
+        end
+
+        def parsed_value(value_, parse_params_)
+          [value_, nil]
+        end
+
+        def unparsed_value(value_, style_, unparse_params_)
+          value_.to_s
+        end
+
+      end
+
+
+      # A recognizer for symbolic fields that recognizes a single regular
+      # expression and maps it to a single particular value.
+
+      class RegexpSymbolRecognizer < RecognizerBase  # :nodoc:
+
+        def initialize(field_, value_, regexp_, canonical_, opts_={})
+          setup(field_, regexp_, opts_)
+          @value = value_
+          @canonical = canonical_
+        end
+
+        def parsed_value(value_, parse_params_)
+          [@value, nil]
+        end
+
+        def unparsed_value(value_, style_, unparse_params_)
+          @canonical
+        end
+
+        def should_unparse?(value_, style_)
+          style_ == @style && value_ == @value
+        end
+
+      end
+
+
+      # A recognizer for symbolic fields that recognizes a mapping of values
+      # to regular expressions.
+
+      class MappingSymbolRecognizer < RecognizerBase  # :nodoc:
+
+        def initialize(field_, opts_={}, &block_)
+          @mappings_in_order = []
+          @mappings_by_value = {}
+          builder_ = Delimiter::MappingSymbolBuilder.new(@mappings_in_order, @mappings_by_value)
+          ::Blockenspiel.invoke(block_, builder_)
+          regexps_ = @mappings_in_order.map{ |map_| "(#{map_[0]})" }
+          setup(field_, regexps_.join('|'), opts_)
+          @mappings_in_order.each do |map_|
+            map_[0] = ::Regexp.new("\\A(#{map_[0]})", @regexp_options)
+          end
+        end
+
+        def parsed_value(value_, parse_params_)
+          @mappings_in_order.each do |map_|
+            return [map_[2], nil] if map_[0].match(value_)
+          end
+          nil
+        end
+
+        def unparsed_value(value_, style_, unparse_params_)
+          @mappings_by_value[value_][1]
+        end
+
+        def should_unparse?(value_, style_)
+          style_ == @style && @mappings_by_value.include?(value_)
+        end
+
+      end
+
+
+    end
+
+
+  end
+
+end
diff --git a/lib/versionomy/format_definitions/rubygems.rb b/lib/versionomy/format_definitions/rubygems.rb
new file mode 100644
index 0000000..26294c7
--- /dev/null
+++ b/lib/versionomy/format_definitions/rubygems.rb
@@ -0,0 +1,343 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy standard format implementation
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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 rubygems 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. 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.
+
+    def self.rubygems
+      get('rubygems')
+    end
+
+
+    # This is a namespace for the implementation of the Rubygems schema
+    # and format.
+
+    module Rubygems
+
+
+      # Extra methods added to version values that use the rubygems schema.
+
+      module ExtraMethods
+
+
+        # Returns true if the version is a prerelease version-- that is,
+        # if any of the fields is non-numeric.
+        #
+        # This behaves the same as the Gem::Version#prerelease? method
+        # in rubygems.
+
+        def prerelease?
+          values_array.any?{ |val_| val_.kind_of?(::String) }
+        end
+
+
+        # Returns the release for this version.
+        # For example, converts "1.2.0.a.1" to "1.2.0".
+        # Non-prerelease versions return themselves.
+        #
+        # This behaves the same as the Gem::Version#release method
+        # in rubygems.
+
+        def release
+          values_ = []
+          self.each_field_object do |field_, val_|
+            break unless val_.kind_of?(::Integer)
+            values_ << val_
+          end
+          Value.new(values_, self.format, self.unparse_params)
+        end
+
+
+        # Returns a list of the field values, in field order, with
+        # trailing zeroes stripped off.
+        #
+        # This behaves the same as the Gem::Version#parts method
+        # in rubygems.
+
+        def parts
+          unless defined?(@parts)
+            @parts = values_array
+            @parts.pop while @parts.size > 1 && @parts.last == 0
+          end
+          @parts
+        end
+
+
+      end
+
+
+      # Create the rubygems format.
+      # This method is called internally when Versionomy loads the rubygems
+      # format, and you should not need to call it again. It is documented
+      # 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 rubygems schema
+        schema_ = Schema.create do
+
+          # Global comparison function
+          to_compare_type(:string) do |a_, b_|
+            if a_.kind_of?(::Integer)
+              if b_.kind_of?(::Integer)
+                a_ <=> b_
+              else
+                1
+              end
+            else
+              if b_.kind_of?(::Integer)
+                -1
+              else
+                a_ <=> b_
+              end
+            end
+          end
+
+          # Global canonicalization function
+          to_canonicalize_type(:string) do |val_|
+            if val_.kind_of?(::Integer)
+              val_
+            else
+              val_ = val_.to_s
+              if val_ =~ /\A\d*\z/
+                val_.to_i
+              else
+                val_
+              end
+            end
+          end
+
+          # The first field has the default value of 1. All other fields
+          # have a default value of 0. Thus, the default version number
+          # overall is "1.0".
+          field(:field0, :type => :integer, :default_value => 1) do
+            field(:field1, :type => :string) do
+              field(:field2, :type => :string) do
+                field(:field3, :type => :string) do
+                  field(:field4, :type => :string) do
+                    field(:field5, :type => :string) do
+                      field(:field6, :type => :string) do
+                        field(:field7, :type => :string)
+                      end
+                    end
+                  end
+                end
+              end
+            end
+          end
+
+          # Some field aliases providing alternate names for major fields
+          alias_field(:major, :field0)
+          alias_field(:minor, :field1)
+
+          # Add the methods in this module to each value
+          add_module(Format::Rubygems::ExtraMethods)
+        end
+
+        # The following is the definition of the rubygems format. It
+        # understands the rubygems schema defined above.
+        Format::Delimiter.new(schema_) do
+
+          # All version number strings must start with the major version.
+          # Unlike other fields, it is not preceded by any delimiter.
+          field(:field0) do
+            recognize_number(:delimiter_regexp => '', :default_delimiter => '')
+          end
+
+          # The remainder of the version number are represented as strings
+          # or 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(:field1) do
+            recognize_regexp('[0-9a-zA-Z]+', :default_value_optional => true)
+          end
+          field(:field2) do
+            recognize_regexp('[0-9a-zA-Z]+', :default_value_optional => true)
+          end
+          field(:field3) do
+            recognize_regexp('[0-9a-zA-Z]+', :default_value_optional => true)
+          end
+          field(:field4) do
+            recognize_regexp('[0-9a-zA-Z]+', :default_value_optional => true)
+          end
+          field(:field5) do
+            recognize_regexp('[0-9a-zA-Z]+', :default_value_optional => true)
+          end
+          field(:field6) do
+            recognize_regexp('[0-9a-zA-Z]+', :default_value_optional => true)
+          end
+          field(:field7) do
+            recognize_regexp('[0-9a-zA-Z]+', :default_value_optional => true)
+          end
+
+          # By default, we require that at least the first two fields
+          # appear in an unparsed version string.
+          default_unparse_params(:required_fields => [:field1])
+        end
+      end
+
+
+    end
+
+
+    register('rubygems', Format::Rubygems.create, true)
+
+
+  end
+
+
+  module Conversion
+
+
+    # This is a namespace for the implementation of the conversion between
+    # the rubygems and standard formats.
+
+    module Rubygems
+
+
+      # Create the conversion from standard to rubygems format.
+      # This method is called internally when Versionomy loads the rubygems
+      # format, and you should not need to call it again. It is documented
+      # so that you can inspect its source code from RDoc, since the source
+      # contains useful examples of how to use the conversion DSLs.
+
+      def self.create_standard_to_rubygems
+
+        # We'll use a parsing conversion.
+        Conversion::Parsing.new do
+
+          # We're going to modify how the standard format version is
+          # unparsed, so the rubygems format will have a better chance
+          # of parsing it.
+          to_modify_unparse_params do |params_, convert_params_|
+
+            params_ ||= {}
+
+            # If the standard format version has a prerelease notation,
+            # make sure it is set off using a delimiter that the rubygems
+            # format can recognize. So instead of "1.0b2", we force the
+            # unparsing to generate "1.0.b.2".
+            params_[:release_type_delim] = '.'
+            params_[:development_version_delim] = '.'
+            params_[:alpha_version_delim] = '.'
+            params_[:beta_version_delim] = '.'
+            params_[:release_candidate_version_delim] = '.'
+            params_[:preview_version_delim] = '.'
+
+            # If the standard format version has a patchlevel notation,
+            # force it to use the default number rather than letter style.
+            # So instead of "1.2c", we force the unparsing to generate
+            # "1.2-3".
+            params_[:patchlevel_style] = nil
+
+            # If the standard format version has a patchlevel notation,
+            # force it to use the default delimiter of "-" so the rubygems
+            # format will recognize it. So instead of "1.9.1p243", we force
+            # the unparsing to generate "1.9.1-243".
+            params_[:patchlevel_delim] = nil
+
+            # If the standard format version includes a "v" prefix, strip
+            # it because rubygems doesn't like it.
+            params_[:major_delim] = nil
+
+            params_
+          end
+
+          # Standard formats sometimes allow hyphens and spaces in field
+          # delimiters, but the rubygems format requires periods. So modify
+          # the unparsed string to conform to rubygems's expectations.
+          to_modify_string do |str_, convert_params_|
+            str_.gsub(/[\.\s-]+/, '.')
+          end
+
+        end
+
+      end
+
+
+      # Create the conversion from rubygems to standard format.
+      # This method is called internally when Versionomy loads the rubygems
+      # format, and you should not need to call it again. It is documented
+      # so that you can inspect its source code from RDoc, since the source
+      # contains useful examples of how to use the conversion DSLs.
+
+      def self.create_rubygems_to_standard
+
+        # We'll use a parsing conversion.
+        Conversion::Parsing.new do
+
+          # Handle the case where the rubygems version ends with a string
+          # field, e.g. "1.0.b". We want to treat this like "1.0b0" rather
+          # than "1.0-2" since the rubygems semantic states that this is a
+          # prerelease version. So we add 0 to the end of the parsed string
+          # if it ends in a letter.
+          to_modify_string do |str_, convert_params_|
+            str_.gsub(/([[:alpha:]])\z/, '\10')
+          end
+
+        end
+
+      end
+
+
+    end
+
+
+    register(:standard, :rubygems, Conversion::Rubygems.create_standard_to_rubygems, true)
+    register(:rubygems, :standard, Conversion::Rubygems.create_rubygems_to_standard, true)
+
+
+  end
+
+
+end
diff --git a/lib/versionomy/format_definitions/semver.rb b/lib/versionomy/format_definitions/semver.rb
new file mode 100644
index 0000000..b755b16
--- /dev/null
+++ b/lib/versionomy/format_definitions/semver.rb
@@ -0,0 +1,272 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy semver format implementation
+#
+# -----------------------------------------------------------------------------
+# Copyright 2010-2012 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 semver format.
+    # This is identical to calling <tt>get('semver')</tt>.
+    #
+    # The semver format is designed to conform to the Semantic Versioning
+    # spec by Tom Preston-Warner. See http://semver.org/
+    #
+    # For the exact annotated definition of the semver schema and format,
+    # see the source code for Versionomy::Format::Semver#create.
+
+    def self.semver
+      get('semver')
+    end
+
+
+    # This is a namespace for the implementation of the semver schema
+    # and format.
+
+    module Semver
+
+
+      # Extra methods added to version values that use the semver schema.
+
+      module ExtraMethods
+
+
+        # Returns true if the version is a prerelease version-- that is,
+        # if the prerelease_suffix is nonempty.
+
+        def prerelease?
+          prerelease_suffix.length > 0
+        end
+
+
+        # Returns the release for this version.
+        # For example, converts "1.2.0a1" to "1.2.0".
+        # Non-prerelease versions return themselves unchanged.
+
+        def release
+          prerelease? ? self.change(:prerelease_suffix => '') : self
+        end
+
+
+        # Returns true if this version is compatible with the given version,
+        # according to the Semantic Versioning specification.
+        # For example, 1.1.0 is compatible with 1.0.0 but not vice versa,
+        # 1.1.1 and 1.1.0 are compatible with each other, while 1.0.0 and
+        # 2.0.0 are mutually incompatible.
+
+        def compatible_with?(version_)
+          self.major == version_.major ? self.minor >= version_.minor : false
+        end
+
+
+      end
+
+
+      # Create the semver format.
+      # This method is called internally when Versionomy loads the semver
+      # format, and you should not need to call it again. It is documented
+      # 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 semver schema
+        schema_ = Schema.create do
+
+          # The first field has the default value of 1. All other fields
+          # have a default value of 0. Thus, the default version number
+          # overall is "1.0".
+          field(:major, :type => :integer, :default_value => 1) do
+            field(:minor, :type => :integer) do
+              field(:patch, :type => :integer) do
+                field(:prerelease_suffix, :type => :string) do
+                  to_compare do |a_, b_|
+                    a_.length == 0 ? (b_.length == 0 ? 0 : 1) : (b_.length == 0 ? -1 : a_ <=> b_)
+                  end
+                end
+              end
+            end
+          end
+
+          # An alias
+          alias_field(:special_suffix, :prerelease_suffix)
+
+          # Add the methods in this module to each value
+          add_module(Format::Semver::ExtraMethods)
+        end
+
+        # The following is the definition of the standard format. It
+        # understands the standard schema defined above.
+        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?', :default_delimiter => '')
+          end
+
+          # The remainder of the core version number are represented as
+          # integers delimited by periods. These fields are required.
+          field(:minor) do
+            recognize_number
+          end
+          field(:patch) do
+            recognize_number
+          end
+
+          # The optional prerelease field is represented as a string
+          # beginning with an alphabetic character.
+          field(:prerelease_suffix) do
+            recognize_regexp('[a-zA-Z][0-9a-zA-Z-]*', :default_value_optional => true,
+                             :delimiter_regexp => '', :default_delimiter => '')
+          end
+        end
+      end
+
+
+    end
+
+
+    register('semver', Format::Semver.create, true)
+
+
+  end
+
+
+  module Conversion
+
+
+    # This is a namespace for the implementation of the conversion between
+    # the semver and standard formats.
+
+    module Semver
+
+
+      # Create the conversion from standard to semver format.
+      # This method is called internally when Versionomy loads the semver
+      # format, and you should not need to call it again. It is documented
+      # so that you can inspect its source code from RDoc, since the source
+      # contains useful examples of how to use the conversion DSLs.
+
+      def self.create_standard_to_semver
+
+        # We'll use a parsing conversion.
+        Conversion::Parsing.new do
+
+          # Sanity check the original value and make sure it doesn't
+          # include fields that we don't support.
+          to_modify_original_value do |value_, convert_params_|
+            if value_.has_field?(:patchlevel) && value_.patchlevel != 0
+              raise Errors::ConversionError, 'Cannot convert a version with a patchlevel to semver'
+            end
+            if value_.tiny2 != 0
+              raise Errors::ConversionError, 'Cannot convert a version more than three fields to semver'
+            end
+            value_
+          end
+
+          # We're going to modify how the standard format version is
+          # unparsed, so the semver format will have a better chance
+          # of parsing it.
+          to_modify_unparse_params do |params_, convert_params_|
+
+            # All three fields are required
+            params_[:minor_required] = true
+            params_[:tiny_required] = true
+
+            # If the standard format version has a prerelease notation,
+            # make sure it isn't set off using a delimiter.
+            params_[:release_type_delim] = ''
+            params_[:development_version_delim] = ''
+            params_[:development_minor_delim] = '-'
+            params_[:alpha_version_delim] = ''
+            params_[:alpha_minor_delim] = '-'
+            params_[:beta_version_delim] = ''
+            params_[:beta_minor_delim] = '-'
+            params_[:release_candidate_version_delim] = ''
+            params_[:release_candidate_minor_delim] = '-'
+            params_[:preview_version_delim] = ''
+            params_[:preview_minor_delim] = '-'
+
+            # If the standard format version includes a "v" prefix, strip it
+            params_[:major_delim] = nil
+
+            params_
+          end
+
+        end
+
+      end
+
+
+      # Create the conversion from semver to standard format.
+      # This method is called internally when Versionomy loads the semver
+      # format, and you should not need to call it again. It is documented
+      # so that you can inspect its source code from RDoc, since the source
+      # contains useful examples of how to use the conversion DSLs.
+
+      def self.create_semver_to_standard
+
+        # We'll use a parsing conversion.
+        Conversion::Parsing.new do
+
+          # Handle the case where the semver version ends with a string
+          # field, e.g. "1.0b". We want to treat this like "1.0b0" rather
+          # than "1.0-2" since the semver semantic states that this is a
+          # prerelease version. So we add 0 to the end of the parsed string
+          # if it ends in a letter.
+          to_modify_string do |str_, convert_params_|
+            str_.gsub(/([[:alpha:]])\z/, '\10')
+          end
+
+        end
+
+      end
+
+
+    end
+
+
+    register(:standard, :semver, Conversion::Semver.create_standard_to_semver, true)
+    register(:semver, :standard, Conversion::Semver.create_semver_to_standard, true)
+
+
+  end
+
+
+end
diff --git a/lib/versionomy/format_definitions/standard.rb b/lib/versionomy/format_definitions/standard.rb
new file mode 100644
index 0000000..bb38330
--- /dev/null
+++ b/lib/versionomy/format_definitions/standard.rb
@@ -0,0 +1,398 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy standard format implementation
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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 <tt>get('standard')</tt>.
+    #
+    # 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:
+    # "<tt>major.minor.tiny.tiny2</tt>".
+    #
+    # The next field, <tt>:release_type</tt>, defines the remaining
+    # structure. The release type can be one of these symbolic values:
+    # <tt>:development</tt>, <tt>:alpha</tt>, <tt>:beta</tt>,
+    # <tt>:preview</tt>, <tt>:release_candidate</tt>, <tt>:release</tt>.
+    #
+    # Depending on that value, additional fields become available. For
+    # example, the <tt>:alpha</tt> value enables the fields
+    # <tt>:alpha_version</tt> and <tt>:alpha_minor</tt>, 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 <tt>:beta</tt> release_type value enables the fields
+    # <tt>:beta_version</tt> and <tt>:beta_minor</tt>. A release_type
+    # of <tt>:release</tt> enables <tt>:patchlevel</tt> and
+    # <tt>:patchlevel_minor</tt>, 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 <tt>:release_type</tt> field can be unparsed as either
+    # <tt>:long</tt> style (e.g. "1.0alpha2") or <tt>:short</tt> style
+    # (e.g. "1.0a2"). The patchlevel field can be unparsed as either
+    # <tt>:number</tt> style (e.g. "2.1-1") or <tt>:letter</tt> 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 loads the standard
+      # format, and you should not need to call it again. It is documented
+      # 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::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 <tt>:final</tt>. 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, true)
+
+
+  end
+
+end
diff --git a/lib/versionomy/interface.rb b/lib/versionomy/interface.rb
new file mode 100644
index 0000000..174914f
--- /dev/null
+++ b/lib/versionomy/interface.rb
@@ -0,0 +1,219 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy convenience interface
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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.
+# -----------------------------------------------------------------------------
+;
+
+
+# == Versionomy
+#
+# The Versionomy module contains some convenience methods for creating and
+# parsing version numbers.
+
+module Versionomy
+
+  @default_format = nil
+
+
+  class << self
+
+
+    # Gets the current default format. Usually this is the "standard"
+    # format returned by Versionomy::Format.standard.
+
+    def default_format
+      @default_format ||= Format.standard
+    end
+
+
+    # Sets the default format used by other methods of this convenience
+    # interface. Usually, this is set to the "standard" format returned by
+    # Versionomy::Format.standard and should not be changed.
+    #
+    # The format can be specified as a format object or the name of a format
+    # registered with Versionomy::Format. If the format is set to nil, the
+    # default_format will be reset to the "standard" format.
+    #
+    # Raises Versionomy::Errors::UnknownFormatError if a name is given that
+    # is not registered.
+
+    def default_format=(format_)
+      if format_.kind_of?(::String) || format_.kind_of?(::Symbol)
+        format_ = Format.get(format_, true)
+      end
+      @default_format = format_
+    end
+
+
+    # Create a new version number given a hash or array of values, and an
+    # optional format.
+    #
+    # The values should either be a hash of field names and values, or an
+    # array of values that will be interpreted in field order.
+    #
+    # The format can be specified as a format object or the name of a format
+    # registered with Versionomy::Format. If the format is omitted or set
+    # to nil, the default_format will be used.
+    #
+    # You can also optionally provide default parameters to be used when
+    # unparsing this value or any derived from it.
+    #
+    # Raises Versionomy::Errors::UnknownFormatError if a name is given that
+    # is not registered.
+
+    def create(values_=nil, format_=nil, unparse_params_=nil)
+      if format_.kind_of?(::Hash) && unparse_params_.nil?
+        unparse_params_ = format_
+        format_ = nil
+      end
+      if format_.kind_of?(::String) || format_.kind_of?(::Symbol)
+        format_ = Format.get(format_, true)
+      end
+      format_ ||= default_format
+      Value.new(values_ || [], format_, unparse_params_)
+    end
+
+
+    # Create a new version number given a string to parse, and an optional
+    # format.
+    #
+    # The format can be specified as a format object or the name of a format
+    # registered with Versionomy::Format. If the format is omitted or set
+    # to nil, the default_format will be used.
+    #
+    # The parameter hash, if present, will be passed as parsing parameters
+    # to the format.
+    #
+    # Raises Versionomy::Errors::UnknownFormatError if a name is given that
+    # is not registered.
+    #
+    # May raise Versionomy::Errors::ParseError if parsing failed.
+
+    def parse(str_, format_=nil, parse_params_=nil)
+      if format_.kind_of?(::Hash) && parse_params_.nil?
+        parse_params_ = format_
+        format_ = nil
+      end
+      if format_.kind_of?(::String) || format_.kind_of?(::Symbol)
+        format_ = Format.get(format_, true)
+      end
+      format_ ||= default_format
+      format_.parse(str_, parse_params_)
+    end
+
+
+    # Convenience method for creating a version number using the Semantic
+    # Versioning format (see http://semver.org/).
+    #
+    # You may pass a string to parse, or a hash with the following keys, all
+    # of which are optional:
+    # <tt>:major</tt>::
+    #   Major version number
+    # <tt>:minor</tt>::
+    #   Minor version number
+    # <tt>:patch</tt>::
+    #   Patch version number
+    # <tt>:prerelease_suffix</tt>::
+    #   A prerelease suffix (e.g. "b2")
+    #
+    # May raise Versionomy::Errors::ParseError if parsing failed.
+
+    def semver(input_)
+      if input_.kind_of?(::Hash)
+        create(input_, :semver)
+      else
+        parse(input_.to_s, :semver)
+      end
+    end
+
+
+    # Get the version of the given module as a Versionomy::Value.
+    # Tries a number of common approaches to embedding version numbers into
+    # modules, such as string or array constants, submodules containing
+    # constants, or module method calls.
+    # Returns the version number, or nil if it wasn't found or couldn't
+    # be interpreted.
+
+    def version_of(mod_)
+      version_ = nil
+      [:VERSION, :VERSION_STRING, :GemVersion].each do |sym_|
+        if mod_.const_defined?(sym_)
+          version_ = mod_.const_get(sym_)
+          break
+        end
+      end
+      if version_.kind_of?(::Module)
+        if version_.const_defined?(:STRING)
+          version_ = version_.const_get(:STRING)
+        elsif version_.const_defined?(:VERSION)
+          version_ = version_.const_get(:VERSION)
+        elsif version_.const_defined?(:MAJOR) && version_.const_defined?(:MINOR) && version_.const_defined?(:TINY)
+          version_ = Value.new([version_.const_get(:MAJOR), version_.const_get(:MINOR), version_.const_get(:TINY)], :standard)
+        end
+      end
+      unless version_.kind_of?(::String) || version_.kind_of?(::Array) || version_.kind_of?(Value)
+        [:version, :release].each do |sym_|
+          if mod_.respond_to?(sym_)
+            version_ = mod_.send(sym_)
+            break
+          end
+        end
+      end
+      if version_.kind_of?(::String)
+        version_ = parse(version_, :standard) rescue nil
+      elsif version_.kind_of?(::Array)
+        version_ = create(version_, :standard) rescue nil
+      elsif !version_.kind_of?(Value)
+        version_ = nil
+      end
+      version_
+    end
+
+
+    # Get the ruby version as a Versionomy::Value, using the builtin
+    # constants RUBY_VERSION and RUBY_PATCHLEVEL.
+
+    def ruby_version
+      @ruby_version ||= begin
+        version_ = parse(::RUBY_VERSION, :standard)
+        if version_.release_type == :final
+          version_ = version_.change({:patchlevel => ::RUBY_PATCHLEVEL},
+            :patchlevel_required => true, :patchlevel_delim => '-p')
+        end
+        version_
+      end
+    end
+
+
+  end
+
+end
diff --git a/lib/versionomy/schema.rb b/lib/versionomy/schema.rb
new file mode 100644
index 0000000..eb016da
--- /dev/null
+++ b/lib/versionomy/schema.rb
@@ -0,0 +1,95 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy schema namespace
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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
+
+
+  # === Version number schema.
+  #
+  # A schema defines the structure and semantics of a version number.
+  # 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
+  # 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 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 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)
+  #
+  # More generally, fields are actually organized into a DAG (directed
+  # acyclic graph) in which the "most significant" field is the root, the
+  # next most significant is a child of that root, and so forth down the
+  # line. The simple schema above, then, is actually represented as a
+  # linked list (a graph with one path), like this:
+  #
+  #  ("major": integer) ->
+  #      ("minor": integer) ->
+  #          ("tiny": integer) ->
+  #              nil
+  #
+  # It is, however, possible for the form of a field to depend on the value
+  # of the previous field. For example, suppose we wanted a schema in which
+  # if the value of the "minor" field is 0, then the "tiny" field doesn't
+  # exist. e.g.
+  #
+  #  ("major": integer) ->
+  #      ("minor": integer) ->
+  #          [value == 0] : nil
+  #          [otherwise]  : ("tiny": integer) ->
+  #              nil
+  #
+  # The Versionomy::Schema::Field class represents a field in this graph.
+  # The Versionomy::Schema::Wrapper class represents a full schema object.
+  #
+  # Generally, you should create schemas using Versionomy::Schema#create.
+  # That method provides a DSL that lets you quickly create the fields.
+
+  module Schema
+  end
+
+
+end
diff --git a/lib/versionomy/schema/field.rb b/lib/versionomy/schema/field.rb
new file mode 100644
index 0000000..6fe7a34
--- /dev/null
+++ b/lib/versionomy/schema/field.rb
@@ -0,0 +1,509 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy schema field class
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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.
+# -----------------------------------------------------------------------------
+;
+
+
+require 'set'
+
+
+module Versionomy
+
+  module Schema
+
+
+    # Objects of this class represent fields in a schema.
+
+    class Field
+
+
+      # Create a field with the given name.
+      #
+      # Recognized options include:
+      #
+      # <tt>:type</tt>::
+      #   Type of field. This should be <tt>:integer</tt>, <tt>:string</tt>,
+      #   or <tt>:symbol</tt>. Default is <tt>:integer</tt>.
+      # <tt>:default_value</tt>::
+      #   Default value for the field if no value is explicitly set. Default
+      #   is 0 for an integer field, the empty string for a string field, or
+      #   the first symbol added for a symbol field.
+      #
+      # You may provide an optional block. Within the block, you may call
+      # methods of Versionomy::Schema::FieldBuilder to further customize the
+      # field, or add child fields.
+      #
+      # Raises Versionomy::Errors::IllegalValueError if the given default
+      # value is not legal.
+
+      def initialize(name_, opts_={}, &block_)
+        @name = name_.to_sym
+        @type = opts_[:type] || :integer
+        if @type == :symbol
+          @symbol_info = ::Hash.new
+          @symbol_order = ::Array.new
+        else
+          @symbol_info = nil
+          @symbol_order = nil
+        end
+        @default_value = opts_[:default_value]
+        @bump_proc = nil
+        @compare_proc = nil
+        @canonicalize_proc = nil
+        master_builder_ = opts_[:master_builder]
+        if master_builder_
+          @bump_proc = master_builder_._get_default_setting(@type, :bump)
+          @compare_proc = master_builder_._get_default_setting(@type, :compare)
+          @canonicalize_proc = master_builder_._get_default_setting(@type, :canonicalize)
+          @default_value ||= master_builder_._get_default_setting(@type, :value)
+        end
+        @ranges = nil
+        @default_child = nil
+        @children = []
+        ::Blockenspiel.invoke(block_, Schema::FieldBuilder.new(self, master_builder_)) if block_
+        @default_value = canonicalize_value(@default_value)
+      end
+
+
+      def _set_default_value(value_)  # :nodoc:
+        @default_value = value_
+      end
+
+      def _add_symbol(symbol_, opts_={})  # :nodoc:
+        if @type != :symbol
+          raise Errors::TypeMismatchError
+        end
+        if @symbol_info.has_key?(symbol_)
+          raise Errors::SymbolRedefinedError
+        end
+        @symbol_info[symbol_] = [@symbol_order.size, opts_[:bump]]
+        @symbol_order << symbol_
+        if @default_value.nil?
+          @default_value = symbol_
+        end
+      end
+
+      def _set_bump_proc(block_)  # :nodoc:
+        @bump_proc = block_
+      end
+
+      def _set_canonicalize_proc(block_)  # :nodoc:
+        @canonicalize_proc = block_
+      end
+
+      def _set_compare_proc(block_)  # :nodoc:
+        @compare_proc = block_
+      end
+
+
+      def inspect   # :nodoc:
+        "#<#{self.class}:0x#{object_id.to_s(16)} name=#{@name}>"
+      end
+
+      def to_s   # :nodoc:
+        inspect
+      end
+
+
+      # The name of the field.
+
+      def name
+        @name
+      end
+
+
+      # The type of the field.
+      # Possible values are <tt>:integer</tt>, <tt>:string</tt>, or
+      # <tt>:symbol</tt>.
+
+      def type
+        @type
+      end
+
+
+      # The default value of the field
+
+      def default_value
+        @default_value
+      end
+
+
+      # Returns a list of possible values for this field, if the type is
+      # <tt>:symbol</tt>. Returns nil for any other type
+
+      def possible_values
+        @symbol_order ? @symbol_order.dup : nil
+      end
+
+
+      # Given a value, bump it to the "next" value.
+      # Utilizes a bump procedure if given;
+      # otherwise uses default behavior depending on the type.
+
+      def bump_value(value_)
+        if @bump_proc
+          nvalue_ = @bump_proc.call(value_)
+          nvalue_ || value_
+        elsif @type == :integer || @type == :string
+          value_.next
+        else
+          info_ = @symbol_info[value_]
+          info_ ? info_[1] || value_ : nil
+        end
+      end
+
+
+      # Perform a standard comparison on two values.
+      # Returns an integer that may be positive, negative, or 0.
+      # Utilizes a comparison procedure if given;
+      # otherwise uses default behavior depending on the type.
+
+      def compare_values(val1_, val2_)
+        if @compare_proc
+          @compare_proc.call(val1_, val2_)
+        elsif @type == :integer || @type == :string
+          val1_ <=> val2_
+        else
+          info1_ = @symbol_info[val1_]
+          info2_ = @symbol_info[val2_]
+          info1_ && info2_ ? info1_[0] <=> info2_[0] : nil
+        end
+      end
+
+
+      # Given a value, return a "canonical" value for this field.
+      # Utilizes a canonicalization procedure if given;
+      # otherwise uses default behavior depending on the type.
+      #
+      # Raises Versionomy::Errors::IllegalValueError if the given value is
+      # not legal.
+
+      def canonicalize_value(value_)
+        orig_value_ = value_
+        if @canonicalize_proc
+          value_ = @canonicalize_proc.call(value_)
+        else
+          case @type
+          when :integer
+            value_ = value_.to_i rescue nil
+          when :string
+            value_ = value_.to_s rescue nil
+          when :symbol
+            value_ = value_.to_sym rescue nil
+          end
+        end
+        if value_.nil? || (@type == :symbol && !@symbol_info.has_key?(value_))
+          raise Errors::IllegalValueError, "#{@name} does not allow the value #{orig_value_.inspect}"
+        end
+        value_
+      end
+
+
+      # Returns the child field associated with the given value.
+      # Returns nil if this field has no child for the given value.
+
+      def child(value_)  # :nodoc:
+        if @ranges
+          @ranges.each do |r_|
+            if !r_[0].nil?
+              cmp_ = compare_values(r_[0], value_)
+              next if cmp_.nil? || cmp_ > 0
+            end
+            if !r_[1].nil?
+              cmp_ = compare_values(r_[1], value_)
+              next if cmp_.nil? || cmp_ < 0
+            end
+            return r_[2]
+          end
+        end
+        @default_child
+      end
+
+
+      # Adds the given child field for the given range.
+      #
+      # If you provide a range of nil, adds the given child field as the
+      # default child for values that do not fall into any other
+      # explicitly specified range.
+      #
+      # Otherwise, the ranges parameter must be an array of "range" objects.
+      # Each of these range objects must be either a single String, Symbol,
+      # or Integer to specify a single value; or a two-element array or a
+      # Range object (only inclusive ends are supported) to specify a range
+      # of values.
+      #
+      # Raises Versionomy::Errors::RangeOverlapError if the specified
+      # range overlaps another previously specified range, or if more than
+      # one default child has been set.
+      #
+      # Raises Versionomy::Errors::RangeSpecificationError if the range
+      # is incorrectly specified.
+      #
+      # Raises Versionomy::Errors::CircularDescendantError if adding this
+      # child will result in a circular reference.
+
+      def add_child(child_, ranges_=nil)
+        if child_._descendant_fields.include?(self)
+          raise Errors::CircularDescendantError
+        end
+        @children << child_
+        if ranges_.nil?
+          if @default_child
+            raise Errors::RangeOverlapError("Cannot have more than one default child")
+          end
+          @default_child = child_
+          return
+        end
+        ranges_ = [ranges_] unless ranges_.is_a?(Array)
+        ranges_.each do |range_|
+          case range_
+          when ::Range
+            if range_.exclude_end?
+              raise Errors::RangeSpecificationError("Ranges must be inclusive")
+            end
+            normalized_range_ = [range_.first, range_.last]
+          when ::Array
+            if range_.size != 2
+              raise Errors::RangeSpecificationError("Range array should have two elements")
+            end
+            normalized_range_ = range_.dup
+          when ::String, ::Symbol, ::Integer
+            normalized_range_ = [range_, range_]
+          else
+            raise Errors::RangeSpecificationError("Unrecognized range type #{range_.class}")
+          end
+          normalized_range_.map! do |elem_|
+            if elem_.nil?
+              elem_
+            else
+              case @type
+              when :integer
+                elem_.to_i
+              when :string
+                elem_.to_s
+              when :symbol
+                begin
+                  elem_.to_sym
+                rescue
+                  raise Errors::RangeSpecificationError("Bad symbol value: #{elem_.inspect}")
+                end
+              end
+            end
+          end
+          normalized_range_ << child_
+          @ranges ||= Array.new
+          insert_index_ = @ranges.size
+          @ranges.each_with_index do |r_, i_|
+            if normalized_range_[0] && r_[1]
+              cmp_ = compare_values(normalized_range_[0], r_[1])
+              if cmp_.nil?
+                raise Errors::RangeSpecificationError
+              end
+              if cmp_ > 0
+                next
+              end
+            end
+            if normalized_range_[1] && r_[0]
+              cmp_ = compare_values(normalized_range_[1], r_[0])
+              if cmp_.nil?
+                raise Errors::RangeSpecificationError
+              end
+              if cmp_ < 0
+                insert_index_ = i_
+                break
+              end
+            end
+            raise Errors::RangeOverlapError
+          end
+          @ranges.insert(insert_index_, normalized_range_)
+        end
+      end
+
+
+      # Compute descendants as a hash of names to fields, including this field.
+
+      def _descendants_by_name  # :nodoc:
+        hash_ = {@name => self}
+        @children.each{ |child_| hash_.merge!(child_._descendants_by_name) }
+        hash_
+      end
+
+
+      # Return a set of all descendant fields, including this field.
+
+      def _descendant_fields(set_=nil)  # :nodoc:
+        set_ ||= Set.new
+        set_ << self
+        @children.each{ |child_| child_._descendant_fields(set_) }
+        set_
+      end
+
+
+    end
+
+
+    # These methods are available in a schema field definition block.
+
+    class FieldBuilder
+
+      include ::Blockenspiel::DSL
+
+      def initialize(field_, master_builder_)  # :nodoc:
+        @field = field_
+        @master_builder = master_builder_
+      end
+
+
+      # Define the given symbol.
+      #
+      # Recognized options include:
+      #
+      # <tt>:bump</tt>::
+      #   The symbol to transition to when "bump" is called.
+      #   Default is to remain on the same value.
+      #
+      # Raises Versionomy::Errors::TypeMismatchError if called when the current field
+      # is not of type <tt>:symbol</tt>.
+      #
+      # Raises Versionomy::Errors::SymbolRedefinedError if the given symbol name is
+      # already defined.
+
+      def symbol(symbol_, opts_={})
+        @field._add_symbol(symbol_, opts_)
+      end
+
+
+      # Provide a default value.
+
+      def default_value(value_)
+        @field._set_default_value(value_)
+      end
+
+
+      # Provide a "bump" procedure.
+      # The given block should take a value, and return the value to transition to.
+      # If you return nil, the value will remain the same.
+
+      def to_bump(&block_)
+        @field._set_bump_proc(block_)
+      end
+
+
+      # Provide a "compare" procedure.
+      # The given block should take two values and compare them.
+      # It should return a negative integer if the first is less than the second,
+      # a positive integer if the first is greater than the second, or 0 if the
+      # two values are equal. If the values cannot be compared, return nil.
+
+      def to_compare(&block_)
+        @field._set_compare_proc(block_)
+      end
+
+
+      # Provide a "canonicalize" procedure.
+      # The given block should take a value and return a canonicalized value.
+      # Return nil if the given value is illegal.
+
+      def to_canonicalize(&block_)
+        @field._set_canonicalize_proc(block_)
+      end
+
+
+      # Add a child field.
+      #
+      # Recognized options include:
+      #
+      # <tt>:only</tt>::
+      #   The child should be available only for the given values of this
+      #   field. See below for ways to specify this constraint.
+      # <tt>:type</tt>::
+      #   Type of field. This should be <tt>:integer</tt>, <tt>:string</tt>,
+      #   or <tt>:symbol</tt>. Default is <tt>:integer</tt>.
+      # <tt>:default_value</tt>::
+      #   Default value for the field if no value is explicitly set. Default
+      #   is 0 for an integer field, the empty string for a string field, or
+      #   the first symbol added for a symbol field.
+      #
+      # You may provide an optional block. Within the block, you may call
+      # methods of this class again to customize the child.
+      #
+      # Raises Versionomy::Errors::IllegalValueError if the given default
+      # value is not legal.
+      #
+      # The <tt>:only</tt> constraint may be specified in one of the
+      # following ways:
+      #
+      # * A single value (integer, string, or symbol)
+      # * The result of calling range() to define an inclusive range of
+      #   integers, strings, or symbols. In this case, either element may be
+      #   nil, specifying an open end of the range. If the field type is
+      #   symbol, the ordering of symbols for the range is defined by the
+      #   order in which the symbols were added to this schema.
+      # * A Range object defining a range of integers or strings.
+      #   Only inclusive, not exclusive, ranges are supported.
+      # * An array of the above.
+      #
+      # Raises Versionomy::Errors::RangeSpecificationError if the given
+      # ranges are not legal.
+      #
+      # Raises Versionomy::Errors::RangeOverlapError if the given ranges
+      # overlap previously specified ranges, or more than one default schema
+      # is specified.
+
+      def field(name_, opts_={}, &block_)
+        only_ = opts_.delete(:only)
+        opts_.merge!(:master_builder => @master_builder)
+        @field.add_child(Schema::Field.new(name_, opts_, &block_), only_)
+      end
+
+
+      # Define a range for the <tt>:only</tt> parameter to +child+.
+      #
+      # This creates an object that +child+ interprets like a standard ruby Range. However, it
+      # is customized for the use of +child+ in the following ways:
+      #
+      # * It supports only inclusive, not exclusive ranges.
+      # * It supports open-ended ranges by setting either endpoint to nil.
+      # * It supports symbol ranges under Ruby 1.8.
+
+      def range(first_, last_)
+        [first_, last_]
+      end
+
+
+    end
+
+
+  end
+
+end
diff --git a/lib/versionomy/schema/wrapper.rb b/lib/versionomy/schema/wrapper.rb
new file mode 100644
index 0000000..e2decca
--- /dev/null
+++ b/lib/versionomy/schema/wrapper.rb
@@ -0,0 +1,321 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy schema wrapper class
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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 Schema
+
+
+    # Creates a schema.
+    # Returns an object of type Versionomy::Schema::Wrapper.
+    #
+    # You may either pass a root field, or provide a block to use to build
+    # fields. If you provide a block, you must use the methods in
+    # Versionomy::Schema::Builder in the block to create the root field.
+
+    def self.create(field_=nil, opts_={}, &block_)
+      if field_ && block_
+        raise ::ArgumentError, 'You may provide either a root field or block but not both'
+      end
+      if block_
+        builder_ = Schema::Builder.new
+        ::Blockenspiel.invoke(block_, builder_)
+        field_ = builder_._get_field
+        modules_ = builder_._get_modules
+        aliases_ = builder_._get_aliases
+      else
+        modules_ = opts_[:modules] || []
+      end
+      Schema::Wrapper.new(field_, modules_, aliases_)
+    end
+
+
+    # Schemas are generally referenced through an object of this class.
+
+    class Wrapper
+
+
+      # Create a new schema wrapper object given a root field.
+      # This is a low-level method. Usually you should call
+      # Versionomy::Schema#create instead.
+
+      def initialize(field_, modules_=[], aliases_={})
+        @root_field = field_
+        @names = @root_field._descendants_by_name
+        @modules = modules_
+        @aliases = {}
+        aliases_.each do |k_,v_|
+          k_ = k_.to_sym
+          v_ = v_.to_sym
+          if @names.include?(v_) && !@names.include?(k_)
+            @aliases[k_] = v_
+          end
+        end
+      end
+
+
+      def inspect   # :nodoc:
+        "#<#{self.class}:0x#{object_id.to_s(16)} root=#{@root_field.inspect}>"
+      end
+
+      def to_s   # :nodoc:
+        inspect
+      end
+
+
+      # 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 value modules.
+      # Note that this is different from the definition of <tt>==</tt>.
+
+      def eql?(obj_)
+        return false unless obj_.kind_of?(Schema::Wrapper)
+        return @root_field == obj_.root_field && @modules == obj_.modules && @aliases == obj_.aliases
+      end
+
+
+      # Returns true if this schema is compatible with the other schema.
+      # 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_)
+        eql?(obj_)
+      end
+
+
+      # If the RHS is a schema, returns true if the schemas are equivalent.
+      # If the RHS is a value, returns true if the value uses this schema.
+
+      def ===(obj_)
+        if obj_.kind_of?(Value)
+          obj_.schema == self
+        else
+          obj_ == self
+        end
+      end
+
+
+      def hash  # :nodoc:
+        @hash ||= @root_field.hash ^ @modules.hash
+      end
+
+
+      # Returns the root (most significant) field in this schema.
+
+      def root_field
+        @root_field
+      end
+
+
+      # Return the canonical field name given a name, or nil if the name
+      # is not recognized.
+
+      def canonical_name(name_)
+        name_ = name_.to_sym
+        name_ = @aliases[name_] || name_
+        @names.include?(name_) ? name_ : nil
+      end
+
+
+      # Return the field with the given name, or nil if the given name
+      # is not found in this schema. If include_aliases_ is set to true,
+      # this also supports lookup by alias.
+
+      def field_named(name_, include_aliases_=false)
+        name_ = name_.to_sym
+        name_ = @aliases[name_] || name_ if include_aliases_
+        @names[name_]
+      end
+
+
+      # Returns an array of names present in this schema, in no particular
+      # order. Does not include aliases.
+
+      def names
+        @names.keys
+      end
+
+
+      # Returns an array of modules that should be included in values that
+      # use this schema.
+
+      def modules
+        @modules.dup
+      end
+
+
+      # Returns a hash of field name aliases.
+
+      def aliases
+        @aliases.dup
+      end
+
+
+    end
+
+
+    # These methods are available in a schema definition block given to
+    # Versionomy::Schema#create.
+
+    class Builder
+
+      include ::Blockenspiel::DSL
+
+      def initialize()  # :nodoc:
+        @field = nil
+        @modules = []
+        @aliases = {}
+        @defaults = { :integer => {}, :string => {}, :symbol => {} }
+      end
+
+
+      # Create the root field.
+      #
+      # Recognized options include:
+      #
+      # <tt>:type</tt>::
+      #   Type of field. This should be <tt>:integer</tt>, <tt>:string</tt>,
+      #   or <tt>:symbol</tt>. Default is <tt>:integer</tt>.
+      # <tt>:default_value</tt>::
+      #   Default value for the field if no value is explicitly set. Default
+      #   is 0 for an integer field, the empty string for a string field, or
+      #   the first symbol added for a symbol field.
+      #
+      # You may provide an optional block. Within the block, you may call
+      # methods of Versionomy::Schema::FieldBuilder to customize this field.
+      #
+      # Raises Versionomy::Errors::IllegalValueError if the given default
+      # value is not legal.
+      #
+      # Raises Versionomy::Errors::RangeOverlapError if a root field has
+      # already been created.
+
+      def field(name_, opts_={}, &block_)
+        if @field
+          raise Errors::RangeOverlapError, "Root field already defined"
+        end
+        @field = Schema::Field.new(name_, opts_.merge(:master_builder => self), &block_)
+      end
+
+
+      # Create a field alias.
+
+      def alias_field(alias_name_, field_name_)
+        @aliases[alias_name_.to_sym] = field_name_.to_sym
+      end
+
+
+      # Add a module to the schema. All values that use this schema will
+      # include this module. This provides a way to add schema-specific
+      # capabilities to version numbers.
+
+      def add_module(mod_)
+        @modules << mod_
+      end
+
+
+      # Provide a default bump procedure for the given type.
+      # The type should be <tt>:integer</tt>, <tt>:string</tt>, or
+      # <tt>:symbol</tt>. You must provide a block that takes a field value
+      # and returns the "bumped" value. This procedure will be used for
+      # all fields of this type, unless explicitly overridden by the field.
+
+      def to_bump_type(type_, &block_)
+        @defaults[type_][:bump] = block_
+      end
+
+
+      # Provide a default compare procedure for the given type.
+      # The type should be <tt>:integer</tt>, <tt>:string</tt>, or
+      # <tt>:symbol</tt>. You must provide a block that takes two values
+      # and returns a standard comparison result-- that is, a negative
+      # integer if the first value is less, 0 if the values are equal, or a
+      # positive integer if the first value is greater. This procedure will
+      # be used for all fields of this type, unless explicitly overridden
+      # by the field.
+
+      def to_compare_type(type_, &block_)
+        @defaults[type_][:compare] = block_
+      end
+
+
+      # Provide a default canonicalization procedure for the given type.
+      # The type should be <tt>:integer</tt>, <tt>:string</tt>, or
+      # <tt>:symbol</tt>. You must provide a block that takes a field value
+      # and returns the canonical value. This procedure will be used for
+      # all fields of this type, unless explicitly overridden by the field.
+
+      def to_canonicalize_type(type_, &block_)
+        @defaults[type_][:canonicalize] = block_
+      end
+
+
+      # Provide a default value for the given type.
+      # The type should be <tt>:integer</tt>, <tt>:string</tt>, or
+      # <tt>:symbol</tt>. You must provide a default value that will be
+      # used for all fields of this type, unless explicitly overridden by
+      # the field.
+
+      def default_value_for_type(type_, value_)
+        @defaults[type_][:value] = value_
+      end
+
+
+      def _get_field  # :nodoc:
+        @field
+      end
+
+      def _get_modules  # :nodoc:
+        @modules
+      end
+
+      def _get_aliases  # :nodoc:
+        @aliases
+      end
+
+      def _get_default_setting(type_, setting_)  # :nodoc:
+        @defaults[type_][setting_]
+      end
+
+    end
+
+
+  end
+
+end
diff --git a/lib/versionomy/value.rb b/lib/versionomy/value.rb
new file mode 100644
index 0000000..433c1f7
--- /dev/null
+++ b/lib/versionomy/value.rb
@@ -0,0 +1,564 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy value
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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.
+# -----------------------------------------------------------------------------
+;
+
+
+begin
+  require 'psych'
+rescue ::LoadError
+  require 'yaml'
+end
+
+
+module Versionomy
+
+
+  # === Version number value
+  #
+  # A version number value is an ordered list of values, corresponding to an
+  # ordered list of fields defined by a schema. For example, if the schema
+  # is a simple one of the form "major.minor.tiny", then the the version
+  # number "1.4.2" would have the values <tt>[1, 4, 2]</tt> in that order,
+  # corresponding to the fields <tt>[:major, :minor, :tiny]</tt>.
+  #
+  # Version number values are comparable with other values that have an
+  # equivalent schema.
+
+  class Value
+
+
+    # Create a value, given a hash or array of values, and a format. Both
+    # these parameters are required.
+    #
+    # The values should either be a hash of field names and values, or an
+    # array of values that will be interpreted in field order.
+    #
+    # You can also optionally provide default unparsing parameters for the
+    # value.
+
+    def initialize(values_, format_, unparse_params_=nil)
+      unless values_.kind_of?(::Hash) || values_.kind_of?(::Array)
+        raise ::ArgumentError, "Expected hash or array but got #{values_.class}"
+      end
+      @_format = format_
+      @_unparse_params = unparse_params_
+      @_field_path = []
+      @_values = {}
+      values_ = _canonicalize_values_hash(values_) if values_.kind_of?(::Hash)
+      field_ = @_format.schema.root_field
+      while field_
+        value_ = values_.kind_of?(::Hash) ? values_[field_.name] : values_.shift
+        value_ = value_ ? field_.canonicalize_value(value_) : field_.default_value
+        @_field_path << field_
+        @_values[field_.name] = value_
+        field_ = field_.child(value_)
+      end
+      modules_ = @_format.schema.modules
+      extend(*modules_) if modules_.size > 0
+    end
+
+
+    def inspect  # :nodoc:
+      begin
+        str_ = unparse
+        "#<#{self.class}:0x#{object_id.to_s(16)} #{str_.inspect}>"
+      rescue Errors::UnparseError
+        _inspect
+      end
+    end
+
+    def _inspect  # :nodoc:
+      "#<#{self.class}:0x#{object_id.to_s(16)} " +
+        @_field_path.map{ |field_| "#{field_.name}=#{@_values[field_.name].inspect}" }.join(' ')
+    end
+
+
+    # Returns a string representation generated by unparsing.
+    # If unparsing fails, does not raise Versionomy::Errors::UnparseError,
+    # but instead returns the string generated by +inspect+.
+
+    def to_s
+      begin
+        unparse
+      rescue Errors::UnparseError
+        _inspect
+      end
+    end
+
+
+    # Unparse this version number and return a string.
+    #
+    # Raises Versionomy::Errors::UnparseError if unparsing failed.
+
+    def unparse(params_=nil)
+      @_format.unparse(self, params_)
+    end
+
+
+    # Return the schema defining the structure and semantics of this
+    # version number.
+
+    def schema
+      @_format.schema
+    end
+
+
+    # Return the format defining the schema and formatting/parsing of
+    # this version number.
+
+    def format
+      @_format
+    end
+
+
+    # Return the unparsing parameters for this value.
+    # Returns nil if this value was not created using a parser.
+
+    def unparse_params
+      @_unparse_params ? @_unparse_params.dup : nil
+    end
+
+
+    # Iterates over each field, in field order, yielding the field name and value.
+
+    def each_field
+      @_field_path.each do |field_|
+        yield(field_, @_values[field_.name])
+      end
+    end
+
+
+    # Iterates over each field, in field order, yielding the
+    # Versionomy::Schema::Field object and value.
+
+    def each_field_object  # :nodoc:
+      @_field_path.each do |field_|
+        yield(field_, @_values[field_.name])
+      end
+    end
+
+
+    # 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 }
+    end
+
+
+    # Returns true if this value contains the given field, which may be specified
+    # as a field object, name, or index.
+
+    def has_field?(field_)
+      case field_
+      when Schema::Field
+        @_field_path.include?(field_)
+      when ::Integer
+        @_field_path.size > field_ && field_ >= 0
+      when ::String, ::Symbol
+        @_values.has_key?(@_format.schema.canonical_name(field_))
+      else
+        raise ::ArgumentError
+      end
+    end
+
+
+    # Returns the value of the given field, or nil if the field is not
+    # recognized. The field may be specified as a field object, field name,
+    # or field index.
+
+    def [](field_)
+      @_values[_interpret_field(field_)]
+    end
+
+
+    # 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] }
+    end
+
+
+    # Returns the value as a hash of values keyed by field name.
+
+    def values_hash
+      @_values.dup
+    end
+
+
+    # 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 unchanged if the field was not recognized or could not
+    # be modified.
+
+    def bump(field_)
+      name_ = _interpret_field(field_)
+      return self unless name_ && @_values.include?(name_)
+      values_ = []
+      @_field_path.each do |fld_|
+        oldval_ = @_values[fld_.name]
+        if fld_.name == name_
+          newval_ = fld_.bump_value(oldval_)
+          return self if newval_ == oldval_
+          values_ << newval_
+          return Value.new(values_, @_format, @_unparse_params)
+        else
+          values_ << oldval_
+        end
+      end
+      self
+    end
+
+
+    # Returns a new version number created by resetting the given field. The
+    # field may be specified as a field object, field name, or field index.
+    # Returns self unchanged if the field was not recognized or could not
+    # be modified.
+
+    def reset(field_)
+      name_ = _interpret_field(field_)
+      return self unless name_ && @_values.include?(name_)
+      values_ = []
+      @_field_path.each do |fld_|
+        oldval_ = @_values[fld_.name]
+        if fld_.name == name_
+          values_ << fld_.default_value
+          return Value.new(values_, @_format, @_unparse_params)
+        else
+          values_ << oldval_
+        end
+      end
+      self
+    end
+
+
+    # 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
+      values_ = _canonicalize_values_hash(values_)
+      Value.new(@_values.merge(values_), @_format, unparse_params_)
+    end
+
+
+    # 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.
+
+    def convert(format_, convert_params_=nil)
+      if format_.kind_of?(::String) || format_.kind_of?(::Symbol)
+        format_ = Format.get(format_)
+      end
+      return self if @_format == format_
+      from_schema_ = @_format.schema
+      to_schema_ = format_.schema
+      if from_schema_ == to_schema_
+        return Value.new(@_values, format_, convert_params_)
+      end
+      conversion_ = Conversion.get(from_schema_, to_schema_)
+      if conversion_
+        conversion_.convert_value(self, format_, convert_params_)
+      else
+        standard_format_ = Format.get(:standard)
+        conversion1_ = Conversion.get(from_schema_, standard_format_)
+        conversion2_ = Conversion.get(standard_format_, to_schema_)
+        if conversion1_ && conversion2_
+          value_ = conversion1_.convert_value(self, standard_format_, convert_params_)
+          conversion2_.convert_value(value_, format_, convert_params_)
+        else
+          raise Errors::UnknownConversionError
+        end
+      end
+    end
+
+
+    def hash  # :nodoc:
+      @_hash ||= @_values.hash
+    end
+
+
+    # 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_)
+      if obj_.kind_of?(::String)
+        obj_ = @_format.parse(obj_) rescue nil
+      end
+      return false unless obj_.kind_of?(Value)
+      index_ = 0
+      obj_.each_field_object do |field_, value_|
+        return false if field_ != @_field_path[index_] || value_ != @_values[field_.name]
+        index_ += 1
+      end
+      true
+    end
+
+
+    # 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,
+    # 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)
+        obj_ = @_format.parse(obj_)
+      end
+      return nil unless obj_.kind_of?(Value)
+      if obj_.schema != @_format.schema
+        begin
+          obj_ = obj_.convert(@_format)
+        rescue
+          return nil
+        end
+      end
+      obj_.each_field_object do |field_, value_|
+        val_ = field_.compare_values(@_values[field_.name], value_)
+        return val_ if val_ != 0
+      end
+      0
+    end
+
+
+    # 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_)
+      unless val_
+        raise Errors::SchemaMismatchError
+      end
+      val_ < 0
+    end
+
+
+    # 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_)
+      unless val_
+        raise Errors::SchemaMismatchError
+      end
+      val_ > 0
+    end
+
+
+    include ::Comparable
+
+
+    # Field values may be retrieved by calling them as methods.
+
+    def method_missing(symbol_)
+      self[symbol_] || super
+    end
+
+
+    # :stopdoc:
+
+
+    # Marshal support
+
+    # Marshal this version number
+    def marshal_dump
+      format_name_ = Format.canonical_name_for(@_format, true)
+      unparsed_data_ = nil
+      if @_format.respond_to?(:unparse_for_serialization)
+        unparsed_data_ = @_format.unparse_for_serialization(self) rescue nil
+      end
+      unparsed_data_ ||= @_format.unparse(self) rescue nil
+      data_ = [format_name_]
+      case unparsed_data_
+      when ::Array
+        data_ << unparsed_data_[0]
+        data_ << unparsed_data_[1] if unparsed_data_[1]
+      when ::String
+        data_ << unparsed_data_
+      else
+        data_ << values_array
+        data_ << @_unparse_params if @_unparse_params
+      end
+      data_
+    end
+
+    # Unmarshal this version number.
+    def marshal_load(data_)
+      format_ = Format.get(data_[0], true)
+      if data_[1].kind_of?(::String)
+        val_ = format_.parse(data_[1], data_[2])
+        initialize(val_.values_array, format_, val_.unparse_params)
+      else
+        initialize(data_[1], format_, data_[2])
+      end
+    end
+
+
+    if defined?(::Psych) && respond_to?(:yaml_tag)
+
+      # YAML support through psych
+
+      # YAML tags. The last one is the canonical one.
+      yaml_tag "tag:danielazuma.com,2009:version"
+      yaml_tag "tag:verse15.com,2009:version"
+
+      # Deserialize a version number from YAML
+      def init_with(coder_)  # :nodoc:
+        format_ = Format.get(coder_['format'], true)
+        value_ = coder_['value']
+        if value_
+          value_ = format_.parse(value_, coder_['parse_params'])
+          initialize(value_.values_array, format_, value_.unparse_params)
+        else
+          initialize(coder_['fields'], format_, coder_['unparse_params'])
+        end
+      end
+
+      # Serialize this version number to YAML format.
+      def encode_with(coder_)  # :nodoc:
+        data_ = marshal_dump
+        coder_['format'] = data_[0]
+        if data_[1].kind_of?(::String)
+          coder_['value'] = data_[1]
+          coder_['parse_params'] = data_[2] if data_[2]
+        else
+          coder_['fields'] = data_[1]
+          coder_['unparse_params'] = data_[2] if data_[2]
+        end
+      end
+
+    elsif respond_to?(:yaml_as)
+
+      # YAML support through syck
+
+      # YAML tags. The last one is the canonical one.
+      yaml_as "tag:danielazuma.com,2009:version"
+      yaml_as "tag:verse15.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: #{data_.inspect}"
+        end
+        format_ = Format.get(data_['format'], true)
+        value_ = data_['value']
+        if value_
+          format_.parse(value_, data_['parse_params'])
+        else
+          Value.new(data_['fields'], format_, data_['unparse_params'])
+        end
+      end
+
+      # 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_|
+            map_.add('format', data_[0])
+            if data_[1].kind_of?(::String)
+              map_.add('value', data_[1])
+              map_.add('parse_params', data_[2]) if data_[2]
+            else
+              map_.add('fields', data_[1])
+              map_.add('unparse_params', data_[2]) if data_[2]
+            end
+          end
+        end
+      end
+
+    end
+
+
+    # :startdoc:
+
+
+    private
+
+    def _interpret_field(field_)   # :nodoc:
+      case field_
+      when Schema::Field
+        @_format.schema.canonical_name(field_.name)
+      when ::Integer
+        field_ = @_field_path[field_]
+        field_ ? field_.name : nil
+      when ::String, ::Symbol
+        @_format.schema.canonical_name(field_)
+      end
+    end
+
+
+    def _canonicalize_values_hash(values_)  # :nodoc:
+      schema_ = @_format.schema
+      new_values_ = {}
+      values_.each do |k_,v_|
+        k_ = schema_.canonical_name(k_)
+        new_values_[k_] = v_ if k_
+      end
+      new_values_
+    end
+
+
+  end
+
+
+end
diff --git a/lib/versionomy/version.rb b/lib/versionomy/version.rb
new file mode 100644
index 0000000..db5bbb2
--- /dev/null
+++ b/lib/versionomy/version.rb
@@ -0,0 +1,45 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy version
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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
+
+  # Current gem version, as a frozen string.
+  VERSION_STRING = ::File.read(::File.dirname(__FILE__)+'/../../Version').strip.freeze
+
+  # Current gem version, as a Versionomy::Value.
+  VERSION = ::Versionomy.parse(VERSION_STRING, :standard)
+
+end
diff --git a/metadata.yml b/metadata.yml
new file mode 100644
index 0000000..7ec8f53
--- /dev/null
+++ b/metadata.yml
@@ -0,0 +1,114 @@
+--- !ruby/object:Gem::Specification
+name: versionomy
+version: !ruby/object:Gem::Version
+  version: 0.4.4
+  prerelease: 
+platform: ruby
+authors:
+- Daniel Azuma
+autorequire: 
+bindir: bin
+cert_chain: []
+date: 2012-06-28 00:00:00.000000000 Z
+dependencies:
+- !ruby/object:Gem::Dependency
+  name: blockenspiel
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: 0.4.5
+  type: :runtime
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: 0.4.5
+description: Versionomy is a generalized version number library. It provides tools
+  to represent, manipulate, parse, and compare version numbers in the wide variety
+  of versioning schemes in use.
+email: dazuma at gmail.com
+executables: []
+extensions: []
+extra_rdoc_files:
+- History.rdoc
+- README.rdoc
+- Versionomy.rdoc
+files:
+- lib/versionomy/conversion/base.rb
+- lib/versionomy/conversion/parsing.rb
+- lib/versionomy/conversion.rb
+- lib/versionomy/errors.rb
+- lib/versionomy/format/base.rb
+- lib/versionomy/format/delimiter.rb
+- lib/versionomy/format.rb
+- lib/versionomy/format_definitions/rubygems.rb
+- lib/versionomy/format_definitions/semver.rb
+- lib/versionomy/format_definitions/standard.rb
+- lib/versionomy/interface.rb
+- lib/versionomy/schema/field.rb
+- lib/versionomy/schema/wrapper.rb
+- lib/versionomy/schema.rb
+- lib/versionomy/value.rb
+- lib/versionomy/version.rb
+- lib/versionomy.rb
+- test/tc_custom_format.rb
+- test/tc_readme_examples.rb
+- test/tc_rubygems_basic.rb
+- test/tc_rubygems_conversions.rb
+- test/tc_semver_basic.rb
+- test/tc_semver_conversions.rb
+- test/tc_standard_basic.rb
+- test/tc_standard_bump.rb
+- test/tc_standard_change.rb
+- test/tc_standard_comparison.rb
+- test/tc_standard_misc.rb
+- test/tc_standard_parse.rb
+- test/tc_standard_reset.rb
+- test/tc_version_of.rb
+- History.rdoc
+- README.rdoc
+- Versionomy.rdoc
+- Version
+homepage: http://dazuma.github.com/versionomy
+licenses: []
+post_install_message: 
+rdoc_options: []
+require_paths:
+- lib
+required_ruby_version: !ruby/object:Gem::Requirement
+  none: false
+  requirements:
+  - - ! '>='
+    - !ruby/object:Gem::Version
+      version: 1.8.7
+required_rubygems_version: !ruby/object:Gem::Requirement
+  none: false
+  requirements:
+  - - ! '>'
+    - !ruby/object:Gem::Version
+      version: 1.3.1
+requirements: []
+rubyforge_project: virtuoso
+rubygems_version: 1.8.24
+signing_key: 
+specification_version: 3
+summary: Versionomy is a generalized version number library.
+test_files:
+- test/tc_custom_format.rb
+- test/tc_readme_examples.rb
+- test/tc_rubygems_basic.rb
+- test/tc_rubygems_conversions.rb
+- test/tc_semver_basic.rb
+- test/tc_semver_conversions.rb
+- test/tc_standard_basic.rb
+- test/tc_standard_bump.rb
+- test/tc_standard_change.rb
+- test/tc_standard_comparison.rb
+- test/tc_standard_misc.rb
+- test/tc_standard_parse.rb
+- test/tc_standard_reset.rb
+- test/tc_version_of.rb
diff --git a/test/tc_custom_format.rb b/test/tc_custom_format.rb
new file mode 100644
index 0000000..c07b004
--- /dev/null
+++ b/test/tc_custom_format.rb
@@ -0,0 +1,66 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy parsing tests on standard schema
+#
+# This file contains tests for parsing on the standard schema
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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.
+# -----------------------------------------------------------------------------
+
+
+require 'test/unit'
+require 'versionomy'
+
+
+module Versionomy
+  module Tests  # :nodoc:
+
+    class TestCustomFormat < ::Test::Unit::TestCase  # :nodoc:
+
+
+      # Test parsing with custom format for patchlevel
+
+      def test_parsing_custom_patchlevel_format
+        format_ = ::Versionomy.default_format.modified_copy do
+          field(:patchlevel, :requires_previous_field => false) do
+            recognize_number(:delimiter_regexp => '\s?sp', :default_delimiter => ' SP')
+          end
+        end
+        value1_ = ::Versionomy.parse('2008 SP2', format_)
+        assert_equal(2, value1_.patchlevel)
+        value2_ = value1_.format.parse('2008 sp3')
+        assert_equal(3, value2_.patchlevel)
+      end
+
+
+    end
+
+  end
+end
diff --git a/test/tc_readme_examples.rb b/test/tc_readme_examples.rb
new file mode 100644
index 0000000..9bc18e1
--- /dev/null
+++ b/test/tc_readme_examples.rb
@@ -0,0 +1,120 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy tests of the README examples
+#
+# This file contains tests to ensure the README is valid
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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.
+# -----------------------------------------------------------------------------
+
+
+require 'test/unit'
+require 'versionomy'
+
+
+module Versionomy
+  module Tests  # :nodoc:
+
+    class TestReadmeExamples < ::Test::Unit::TestCase  # :nodoc:
+
+
+      # Test the README file.
+      # This actually reads the README file and does some eval magic
+      # to run it and ensure it works the way it claims.
+
+      def test_readme_file
+        binding_ = _get_binding
+        ::File.open("#{::File.dirname(__FILE__)}/../README.rdoc") do |io_|
+          running_ = false
+          buffer_ = ''
+          buffer_start_line_ = nil
+          io_.each_line do |line_|
+
+            # Run code in the "Some examples" section.
+            running_ = false if line_ =~ /^===/
+            running_ = true if line_ =~ /^=== Some examples/
+            next unless running_ && line_[0,1] == ' '
+            # Skip the require line
+            next if line_ =~ /^\s+require/
+
+            # If there isn't an expects clause, then collect the code into
+            # a buffer to run all at once, because it might be code that
+            # gets spread over multiple lines.
+            delim_index_ = line_.index(' # ')
+            if !delim_index_ || line_[0, delim_index_].strip.length == 0
+              buffer_start_line_ ||= io_.lineno
+              buffer_ << line_
+              next
+            end
+
+            # At this point, we have an expects clause. First run any buffer
+            # accumulated up to now.
+            if buffer_.length > 0
+              ::Kernel.eval(buffer_, binding_, 'README.rdoc', buffer_start_line_)
+              buffer_ = ''
+              buffer_start_line_ = nil
+            end
+
+            # Parse the line into an expression and an expectation
+            expr_ = line_[0,delim_index_]
+            expect_ = line_[delim_index_+3..-1]
+
+            if expect_ =~ /^=> (.*)$/
+              # Expect a value
+              expect_value_ = ::Kernel.eval($1, binding_, 'README.rdoc', io_.lineno)
+              actual_value_ = ::Kernel.eval(expr_, binding_, 'README.rdoc', io_.lineno)
+              assert_equal(expect_value_, actual_value_,
+                           "Values did not match on line #{io_.lineno} of README.rdoc")
+
+            elsif expect_ =~ /^raises (.*)$/
+              # Expect an exception to be raised
+              expect_error_ = ::Kernel.eval($1, binding_, 'README.rdoc', io_.lineno)
+              assert_raise(expect_error_) do
+                ::Kernel.eval(expr_, binding_, 'README.rdoc', io_.lineno)
+              end
+
+            else
+              raise "Unknown expect syntax: #{expect_.inspect}"
+            end
+
+          end
+        end
+      end
+
+
+      def _get_binding
+        binding
+      end
+
+
+    end
+
+  end
+end
diff --git a/test/tc_rubygems_basic.rb b/test/tc_rubygems_basic.rb
new file mode 100644
index 0000000..d64f3ce
--- /dev/null
+++ b/test/tc_rubygems_basic.rb
@@ -0,0 +1,245 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy basic tests on rubygems schema
+#
+# This file contains tests for the basic use cases on the rubygems schema
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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.
+# -----------------------------------------------------------------------------
+
+
+require 'test/unit'
+require 'versionomy'
+
+
+module Versionomy
+  module Tests  # :nodoc:
+
+    class TestRubygemsBasic < ::Test::Unit::TestCase  # :nodoc:
+
+
+      # Test the default version value.
+
+      def test_default_value
+        value_ = ::Versionomy.create(nil, :rubygems)
+        assert_equal(1, value_.field0)
+        assert_equal(0, value_.field1)
+        assert_equal(0, value_.field2)
+        assert_equal(0, value_.field3)
+        assert_equal(0, value_.field4)
+        assert_equal(0, value_.field5)
+        assert_equal(0, value_.field6)
+        assert_equal(0, value_.field7)
+      end
+
+
+      # Test an arbitrary value.
+
+      def test_arbitrary_value
+        value_ = ::Versionomy.create([1, 9, 2, 'pre', 2], :rubygems)
+        assert_equal(1, value_.field0)
+        assert_equal(9, value_.field1)
+        assert_equal(2, value_.field2)
+        assert_equal('pre', value_.field3)
+        assert_equal(2, value_.field4)
+        assert_equal(0, value_.field5)
+        assert_equal(0, value_.field6)
+        assert_equal(0, value_.field7)
+      end
+
+
+      # Test aliases
+
+      def test_alias_fields
+        value_ = ::Versionomy.create([1, 9, 2, 'a', 2], :rubygems)
+        assert_equal(1, value_.major)
+        assert_equal(9, value_.minor)
+      end
+
+
+      # Test construction using aliases
+
+      def test_alias_field_construction
+        value_ = ::Versionomy.create({:major => 1, :minor => 9, :field2 => 2}, :rubygems)
+        assert_equal([1, 9, 2, 0, 0, 0, 0, 0], value_.values_array)
+      end
+
+
+      # Test comparison of numeric values.
+
+      def test_numeric_comparison
+        value1_ = ::Versionomy.create([1, 9, 2], :rubygems)
+        value2_ = ::Versionomy.create([1, 9], :rubygems)
+        assert(value2_ < value1_)
+        value1_ = ::Versionomy.create([1, 9, 0], :rubygems)
+        value2_ = ::Versionomy.create([1, 9], :rubygems)
+        assert(value2_ == value1_)
+      end
+
+
+      # Test comparison of string values.
+
+      def test_string_comparison
+        value1_ = ::Versionomy.create([1, 9, 2, 'a', 2], :rubygems)
+        value2_ = ::Versionomy.create([1, 9, 2, 'b', 1], :rubygems)
+        assert(value2_ > value1_)
+      end
+
+
+      # Test comparison of numeric and string values.
+
+      def test_numeric_and_string_comparison
+        value1_ = ::Versionomy.create([1, 9, 2, 'a', 2], :rubygems)
+        value2_ = ::Versionomy.create([1, 9, 2, 1], :rubygems)
+        assert(value2_ > value1_)
+      end
+
+
+      # Test parsing numeric.
+
+      def test_parsing_numeric
+        value_ = ::Versionomy.parse('2.0.1.1.4.6', :rubygems)
+        assert_equal([2, 0, 1, 1, 4, 6, 0, 0], value_.values_array)
+        assert_equal('2.0.1.1.4.6', value_.unparse)
+      end
+
+
+      # Test parsing with a string.
+
+      def test_parsing_with_string
+        value_ = ::Versionomy.parse('1.9.2.pre.2', :rubygems)
+        assert_equal([1, 9, 2, 'pre', 2, 0, 0, 0], value_.values_array)
+        assert_equal('1.9.2.pre.2', value_.unparse)
+      end
+
+
+      # Test parsing with trailing zeros.
+
+      def test_parsing_trailing_zeros
+        value_ = ::Versionomy.parse('2.0.0', :rubygems)
+        assert_equal([2, 0, 0, 0, 0, 0, 0, 0], value_.values_array)
+        assert_equal('2.0.0', value_.unparse)
+        assert_equal('2.0.0.0.0', value_.unparse(:required_fields => [:field4]))
+        assert_equal('2.0', value_.unparse(:optional_fields => [:field2]))
+      end
+
+
+      # Test bumping a numeric field.
+
+      def test_bump_numeric
+        value_ = ::Versionomy.create([1, 9, 2, 'a', 2], :rubygems)
+        value_ = value_.bump(:field2)
+        assert_equal([1, 9, 3, 0, 0, 0, 0, 0], value_.values_array)
+      end
+
+
+      # Test bumping a string field.
+
+      def test_bump_string
+        value_ = ::Versionomy.create([1, 9, 2, 'a', 2], :rubygems)
+        value_ = value_.bump(:field3)
+        assert_equal([1, 9, 2, 'b', 0, 0, 0, 0], value_.values_array)
+      end
+
+
+      # Test bumping an alias field.
+
+      def test_bump_alias
+        value_ = ::Versionomy.create([1, 9, 2, 'a', 2], :rubygems)
+        value_ = value_.bump(:minor)
+        assert_equal([1, 10, 0, 0, 0, 0, 0, 0], value_.values_array)
+      end
+
+
+      # Test changing an alias field.
+
+      def test_change_alias
+        value_ = ::Versionomy.create([1, 8, 7, 'a', 2], :rubygems)
+        value_ = value_.change(:minor => 9)
+        assert_equal([1, 9, 7, 'a', 2, 0, 0, 0], value_.values_array)
+      end
+
+
+      # Test "prerelase?" custom method
+
+      def test_method_prereleasep
+        value_ = ::Versionomy.create([1, 9, 2, 'a', 2], :rubygems)
+        assert_equal(true, value_.prerelease?)
+        value_ = ::Versionomy.create([1, 9, 2, 2], :rubygems)
+        assert_equal(false, value_.prerelease?)
+      end
+
+
+      # Test "relase" custom method
+
+      def test_method_release
+        value_ = ::Versionomy.create([1, 9, 2, 'a', 2], :rubygems)
+        value2_ = value_.release
+        assert_equal([1, 9, 2, 0, 0, 0, 0, 0], value2_.values_array)
+        value_ = ::Versionomy.create([1, 9, 2, 5, 2], :rubygems)
+        value2_ = value_.release
+        assert_equal(value_, value2_)
+      end
+
+
+      # Test "parts" custom method
+
+      def test_method_parts
+        value_ = ::Versionomy.create([1, 9, 2, 'a', 2], :rubygems)
+        assert_equal([1, 9, 2, 'a', 2], value_.parts)
+        value_ = ::Versionomy.create([1, 9, 0], :rubygems)
+        assert_equal([1, 9], value_.parts)
+      end
+
+
+      # Test marshalling
+
+      def test_marshal
+        value_ = ::Versionomy.create([1, 9, 2, 'a', 2], :rubygems)
+        str_ = ::Marshal.dump(value_)
+        value2_ = ::Marshal.load(str_)
+        assert_equal(value_, value2_)
+      end
+
+
+      # Test YAML
+
+      def test_yaml
+        value_ = ::Versionomy.create([1, 9, 2, 'a', 2], :rubygems)
+        str_ = ::YAML.dump(value_)
+        value2_ = ::YAML.load(str_)
+        assert_equal(value_, value2_)
+      end
+
+
+    end
+
+  end
+end
diff --git a/test/tc_rubygems_conversions.rb b/test/tc_rubygems_conversions.rb
new file mode 100644
index 0000000..06602cf
--- /dev/null
+++ b/test/tc_rubygems_conversions.rb
@@ -0,0 +1,190 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy tests on rubygems schema conversions
+#
+# This file contains tests converting to and from the rubygems schema
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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.
+# -----------------------------------------------------------------------------
+
+
+require 'test/unit'
+require 'versionomy'
+
+
+module Versionomy
+  module Tests  # :nodoc:
+
+    class TestRubygemsConversions < ::Test::Unit::TestCase  # :nodoc:
+
+
+      def setup
+        @standard_format = Format.get(:standard)
+        @rubygems_format = Format.get(:rubygems)
+      end
+
+
+      # Test simple conversion from standard to rubygems.
+
+      def test_standard_to_rubygems_simple
+        value_ = ::Versionomy.parse('1.2')
+        value2_ = value_.convert(:rubygems)
+        assert_equal(@rubygems_format, value2_.format)
+        assert_equal([1, 2, 0, 0, 0, 0, 0, 0], value2_.values_array)
+        value_ = ::Versionomy.parse('1.2.4.1')
+        value2_ = value_.convert(:rubygems)
+        assert_equal(@rubygems_format, value2_.format)
+        assert_equal([1, 2, 4, 1, 0, 0, 0, 0], value2_.values_array)
+      end
+
+
+      # Test conversion from standard to rubygems including a patchlevel
+
+      def test_standard_to_rubygems_with_patchlevel
+        value_ = ::Versionomy.parse('1.2-3')
+        value2_ = value_.convert(:rubygems)
+        assert_equal([1, 2, 3, 0, 0, 0, 0, 0], value2_.values_array)
+        value_ = ::Versionomy.parse('1.2p3')
+        value2_ = value_.convert(:rubygems)
+        assert_equal([1, 2, 3, 0, 0, 0, 0, 0], value2_.values_array)
+        value_ = ::Versionomy.parse('1.2c')
+        value2_ = value_.convert(:rubygems)
+        assert_equal([1, 2, 3, 0, 0, 0, 0, 0], value2_.values_array)
+      end
+
+
+      # Test conversion from standard to rubygems with a beta version
+
+      def test_standard_to_rubygems_beta
+        value_ = ::Versionomy.parse('1.2b3')
+        value2_ = value_.convert(:rubygems)
+        assert_equal([1, 2, 'b', 3, 0, 0, 0, 0], value2_.values_array)
+        value_ = ::Versionomy.parse('1.2 beta 3.4')
+        value2_ = value_.convert(:rubygems)
+        assert_equal([1, 2, 'beta', 3, 4, 0, 0, 0], value2_.values_array)
+      end
+
+
+      # Test conversion from standard to rubygems with a "v" prefix
+
+      def test_standard_to_rubygems_with_v
+        value_ = ::Versionomy.parse('v1.2b3')
+        value2_ = value_.convert(:rubygems)
+        assert_equal([1, 2, 'b', 3, 0, 0, 0, 0], value2_.values_array)
+        value_ = ::Versionomy.parse('V 1.2b3')
+        value2_ = value_.convert(:rubygems)
+        assert_equal([1, 2, 'b', 3, 0, 0, 0, 0], value2_.values_array)
+      end
+
+
+      # Test simple conversion from rubygems to standard.
+
+      def test_rubygems_to_standard_simple
+        value_ = ::Versionomy.parse('1.2', :rubygems)
+        value2_ = value_.convert(:standard)
+        assert_equal(@standard_format, value2_.format)
+        assert_equal([1, 2, 0, 0, :final, 0, 0], value2_.values_array)
+        value_ = ::Versionomy.parse('1.2.4.1', :rubygems)
+        value2_ = value_.convert(:standard)
+        assert_equal(@standard_format, value2_.format)
+        assert_equal([1, 2, 4, 1, :final, 0, 0], value2_.values_array)
+      end
+
+
+      # Test conversion from rubygems to standard with a beta version
+
+      def test_rubygems_to_standard_beta
+        value_ = ::Versionomy.parse('1.2.b.3', :rubygems)
+        value2_ = value_.convert(:standard)
+        assert_equal([1, 2, 0, 0, :beta, 3, 0], value2_.values_array)
+        value_ = ::Versionomy.parse('1.2.b3', :rubygems)
+        value2_ = value_.convert(:standard)
+        assert_equal([1, 2, 0, 0, :beta, 3, 0], value2_.values_array)
+        value_ = ::Versionomy.parse('1.2.beta3', :rubygems)
+        value2_ = value_.convert(:standard)
+        assert_equal([1, 2, 0, 0, :beta, 3, 0], value2_.values_array)
+        value_ = ::Versionomy.parse('1.2.b', :rubygems)
+        value2_ = value_.convert(:standard)
+        assert_equal([1, 2, 0, 0, :beta, 0, 0], value2_.values_array)
+      end
+
+
+      # Test conversion from rubygems to standard with an expectation of failure
+
+      def test_rubygems_to_standard_fail
+        value_ = ::Versionomy.parse('1.2.b.3.4.5', :rubygems)
+        assert_raise(::Versionomy::Errors::ConversionError) do
+          value_.convert(:standard)
+        end
+        value_ = ::Versionomy.parse('1.2.c.3', :rubygems)
+        assert_raise(::Versionomy::Errors::ConversionError) do
+          value_.convert(:standard)
+        end
+      end
+
+
+      # Test equality comparisons between rubygems and standard
+
+      def test_rubygems_to_standard_equality_comparison
+        assert_operator(::Versionomy.parse('1.2.0', :rubygems), :==, ::Versionomy.parse('1.2'))
+        assert_operator(::Versionomy.parse('1.2.b.3', :rubygems), :==, ::Versionomy.parse('1.2b3'))
+      end
+
+
+      # Test inequality comparisons between rubygems and standard
+
+      def test_rubygems_to_standard_inequality_comparison
+        assert_operator(::Versionomy.parse('1.2.3', :rubygems), :<, ::Versionomy.parse('1.2.4'))
+        assert_operator(::Versionomy.parse('1.2.b.3', :rubygems), :>, ::Versionomy.parse('1.2b2'))
+        assert_operator(::Versionomy.parse('1.2', :rubygems), :>, ::Versionomy.parse('1.2b1'))
+      end
+
+
+      # Test equality comparisons between standard and rubygems
+
+      def test_standard_to_rubygems_equality_comparison
+        assert_operator(::Versionomy.parse('1.2.0'), :==, ::Versionomy.parse('1.2', :rubygems))
+        assert_operator(::Versionomy.parse('1.2b3'), :==, ::Versionomy.parse('1.2.beta.3', :rubygems))
+      end
+
+
+      # Test inequality comparisons between standard and rubygems
+
+      def test_standard_to_rubygems_inequality_comparison
+        assert_operator(::Versionomy.parse('1.2.4'), :>, ::Versionomy.parse('1.2.3', :rubygems))
+        assert_operator(::Versionomy.parse('1.2b2'), :<, ::Versionomy.parse('1.2.beta.3', :rubygems))
+        assert_operator(::Versionomy.parse('1.2b2'), :<, ::Versionomy.parse('1.2', :rubygems))
+      end
+
+
+    end
+
+  end
+end
diff --git a/test/tc_semver_basic.rb b/test/tc_semver_basic.rb
new file mode 100644
index 0000000..24eb341
--- /dev/null
+++ b/test/tc_semver_basic.rb
@@ -0,0 +1,213 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy basic tests on rubygems schema
+#
+# This file contains tests for the basic use cases on the rubygems schema
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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.
+# -----------------------------------------------------------------------------
+
+
+require 'test/unit'
+require 'versionomy'
+
+
+module Versionomy
+  module Tests  # :nodoc:
+
+    class TestSemverBasic < ::Test::Unit::TestCase  # :nodoc:
+
+
+      # Test the default version value.
+
+      def test_default_value
+        value_ = ::Versionomy.create(nil, :semver)
+        assert_equal(1, value_.major)
+        assert_equal(0, value_.minor)
+        assert_equal(0, value_.patch)
+        assert_equal('', value_.prerelease_suffix)
+      end
+
+
+      # Test an arbitrary value.
+
+      def test_arbitrary_value
+        value_ = ::Versionomy.create([1, 9, 2, 'pre2'], :semver)
+        assert_equal(1, value_.major)
+        assert_equal(9, value_.minor)
+        assert_equal(2, value_.patch)
+        assert_equal('pre2', value_.prerelease_suffix)
+      end
+
+
+      # Test aliases
+
+      def test_alias_fields
+        value_ = ::Versionomy.create([1, 9, 2, 'pre2'], :semver)
+        assert_equal('pre2', value_.special_suffix)
+      end
+
+
+      # Test construction using aliases
+
+      def test_alias_field_construction
+        value_ = ::Versionomy.create({:major => 1, :minor => 9, :special_suffix => 'pre2'}, :semver)
+        assert_equal([1, 9, 0, 'pre2'], value_.values_array)
+      end
+
+
+      # Test comparison of numeric values.
+
+      def test_numeric_comparison
+        value1_ = ::Versionomy.create([1, 9, 2], :semver)
+        value2_ = ::Versionomy.create([1, 9], :semver)
+        assert(value2_ < value1_)
+        value1_ = ::Versionomy.create([1, 9, 0], :semver)
+        value2_ = ::Versionomy.create([1, 9], :semver)
+        assert(value2_ == value1_)
+      end
+
+
+      # Test comparison of string values.
+
+      def test_string_comparison
+        value1_ = ::Versionomy.create([1, 9, 2, 'a2'], :semver)
+        value2_ = ::Versionomy.create([1, 9, 2, 'b1'], :semver)
+        assert(value2_ > value1_)
+      end
+
+
+      # Test comparison of numeric and string values.
+
+      def test_numeric_and_string_comparison
+        value1_ = ::Versionomy.create([1, 9, 2, 'a2'], :semver)
+        value2_ = ::Versionomy.create([1, 9, 2], :semver)
+        assert(value2_ > value1_)
+      end
+
+
+      # Test parsing numeric.
+
+      def test_parsing_numeric
+        value_ = ::Versionomy.parse('2.0.1', :semver)
+        assert_equal([2, 0, 1, ''], value_.values_array)
+        assert_equal('2.0.1', value_.unparse)
+      end
+
+
+      # Test parsing with a string.
+
+      def test_parsing_with_string
+        value_ = ::Versionomy.parse('1.9.2pre2', :semver)
+        assert_equal([1, 9, 2, 'pre2'], value_.values_array)
+        assert_equal('1.9.2pre2', value_.unparse)
+      end
+
+
+      # Test making sure unparsing requires all three fields.
+
+      def test_parsing_require_all_fields
+        value_ = ::Versionomy.parse('2.0', :semver)
+        assert_equal([2, 0, 0, ''], value_.values_array)
+        assert_equal('2.0.0', value_.unparse)
+        assert_raise(::Versionomy::Errors::ParseError) do
+          value_ = ::Versionomy.parse('2.0b1', :semver)
+        end
+      end
+
+
+      # Test convenience parsing
+
+      def test_convenience_parse
+        value_ = ::Versionomy.semver('2.0.1')
+        assert_equal([2, 0, 1, ''], value_.values_array)
+      end
+
+
+      # Test convenience creation from hash
+
+      def test_convenience_create
+        value_ = ::Versionomy.semver(:major => 2, :patch => 1, :prerelease_suffix => 'b2')
+        assert_equal([2, 0, 1, 'b2'], value_.values_array)
+      end
+
+
+      # Test bumping a numeric field.
+
+      def test_bump_numeric
+        value_ = ::Versionomy.create([1, 9, 2, 'a2'], :semver)
+        value_ = value_.bump(:patch)
+        assert_equal([1, 9, 3, ''], value_.values_array)
+      end
+
+
+      # Test bumping a string field.
+
+      def test_bump_string
+        value_ = ::Versionomy.create([1, 9, 2, 'a2'], :semver)
+        value_ = value_.bump(:prerelease_suffix)
+        assert_equal([1, 9, 2, 'a3'], value_.values_array)
+      end
+
+
+      # Test "prerelase?" custom method
+
+      def test_method_prereleasep
+        value_ = ::Versionomy.create([1, 9, 2, 'a2'], :semver)
+        assert_equal(true, value_.prerelease?)
+        value_ = ::Versionomy.create([1, 9, 2], :semver)
+        assert_equal(false, value_.prerelease?)
+      end
+
+
+      # Test marshalling
+
+      def test_marshal
+        value_ = ::Versionomy.create([1, 9, 2, 'pre2'], :semver)
+        str_ = ::Marshal.dump(value_)
+        value2_ = ::Marshal.load(str_)
+        assert_equal(value_, value2_)
+      end
+
+
+      # Test YAML
+
+      def test_yaml
+        value_ = ::Versionomy.create([1, 9, 2, 'pre2'], :semver)
+        str_ = ::YAML.dump(value_)
+        value2_ = ::YAML.load(str_)
+        assert_equal(value_, value2_)
+      end
+
+
+    end
+
+  end
+end
diff --git a/test/tc_semver_conversions.rb b/test/tc_semver_conversions.rb
new file mode 100644
index 0000000..2f27659
--- /dev/null
+++ b/test/tc_semver_conversions.rb
@@ -0,0 +1,196 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy tests on rubygems schema conversions
+#
+# This file contains tests converting to and from the rubygems schema
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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.
+# -----------------------------------------------------------------------------
+
+
+require 'test/unit'
+require 'versionomy'
+
+
+module Versionomy
+  module Tests  # :nodoc:
+
+    class TestSemverConversions < ::Test::Unit::TestCase  # :nodoc:
+
+
+      def setup
+        @standard_format = Format.get(:standard)
+        @semver_format = Format.get(:semver)
+      end
+
+
+      # Test simple conversion from standard to semver.
+
+      def test_standard_to_semver_simple
+        value_ = ::Versionomy.parse('1.2')
+        value2_ = value_.convert(:semver)
+        assert_equal(@semver_format, value2_.format)
+        assert_equal([1, 2, 0, ''], value2_.values_array)
+        value_ = ::Versionomy.parse('1.2.4')
+        value2_ = value_.convert(:semver)
+        assert_equal(@semver_format, value2_.format)
+        assert_equal([1, 2, 4, ''], value2_.values_array)
+      end
+
+
+      # Test conversion from standard to semver with a beta version
+
+      def test_standard_to_semver_beta
+        value_ = ::Versionomy.parse('1.2b3')
+        value2_ = value_.convert(:semver)
+        assert_equal([1, 2, 0, 'b3'], value2_.values_array)
+        value_ = ::Versionomy.parse('1.2 beta 3')
+        value2_ = value_.convert(:semver)
+        assert_equal([1, 2, 0, 'beta3'], value2_.values_array)
+      end
+
+
+      # Test conversion from standard to semver with a "v" prefix
+
+      def test_standard_to_semver_with_v
+        value_ = ::Versionomy.parse('v1.2.3')
+        value2_ = value_.convert(:semver)
+        assert_equal([1, 2, 3, ''], value2_.values_array)
+        value_ = ::Versionomy.parse('V 1.2.3')
+        value2_ = value_.convert(:semver)
+        assert_equal([1, 2, 3, ''], value2_.values_array)
+      end
+
+
+      # Test conversion from standard to semver with an expectation of failure
+
+      def test_standard_to_semver_fail
+        value_ = ::Versionomy.parse('1.2.3.4', :standard)
+        assert_raise(::Versionomy::Errors::ConversionError) do
+          value_.convert(:semver)
+        end
+      end
+
+
+      # Test simple conversion from semver to standard.
+
+      def test_semver_to_standard_simple
+        value_ = ::Versionomy.parse('1.2', :semver)
+        value2_ = value_.convert(:standard)
+        assert_equal(@standard_format, value2_.format)
+        assert_equal([1, 2, 0, 0, :final, 0, 0], value2_.values_array)
+        value_ = ::Versionomy.parse('1.2.4', :semver)
+        value2_ = value_.convert(:standard)
+        assert_equal(@standard_format, value2_.format)
+        assert_equal([1, 2, 4, 0, :final, 0, 0], value2_.values_array)
+      end
+
+
+      # Test conversion from semver to standard with a beta version
+
+      def test_semver_to_standard_beta
+        value_ = ::Versionomy.parse('1.2.0b3', :semver)
+        value2_ = value_.convert(:standard)
+        assert_equal([1, 2, 0, 0, :beta, 3, 0], value2_.values_array)
+        value_ = ::Versionomy.parse('1.2.4beta3', :semver)
+        value2_ = value_.convert(:standard)
+        assert_equal([1, 2, 4, 0, :beta, 3, 0], value2_.values_array)
+      end
+
+
+      # Test conversion from rubygems to standard with an expectation of failure
+
+      def test_semver_to_standard_fail
+        value_ = ::Versionomy.parse('1.2.3c4', :semver)
+        assert_raise(::Versionomy::Errors::ConversionError) do
+          value_.convert(:standard)
+        end
+      end
+
+
+      # Test conversion when there aren't unparse_params
+
+      def test_standard_to_semver_without_unparse_params
+        value_ = ::Versionomy.create([1,2,3], :standard)
+        value2_ = value_.convert(:semver)
+        assert_equal([1, 2, 3, ''], value2_.values_array)
+      end
+
+
+      # Test conversion between semver and rubygems
+
+      def test_semver_and_rubygems
+        value_ = ::Versionomy.create([1,2,3], :semver)
+        value2_ = value_.convert(:rubygems)
+        assert_equal([1, 2, 3, 0, 0, 0, 0, 0], value2_.values_array)
+        value_ = ::Versionomy.create([1,2,3], :rubygems)
+        value2_ = value_.convert(:semver)
+        assert_equal([1, 2, 3, ''], value2_.values_array)
+      end
+
+
+      # Test equality comparisons between semver and standard
+
+      def test_semver_to_standard_equality_comparison
+        assert_operator(::Versionomy.parse('1.2.0', :semver), :==, ::Versionomy.parse('1.2'))
+        assert_operator(::Versionomy.parse('1.2.0b3', :semver), :==, ::Versionomy.parse('1.2b3'))
+      end
+
+
+      # Test inequality comparisons between semver and standard
+
+      def test_semver_to_standard_inequality_comparison
+        assert_operator(::Versionomy.parse('1.2.3', :semver), :<, ::Versionomy.parse('1.2.4'))
+        assert_operator(::Versionomy.parse('1.2.0b3', :semver), :>, ::Versionomy.parse('1.2b2'))
+        assert_operator(::Versionomy.parse('1.2.0', :semver), :>, ::Versionomy.parse('1.2b1'))
+      end
+
+
+      # Test equality comparisons between standard and rubygems
+
+      def test_standard_to_semver_equality_comparison
+        assert_operator(::Versionomy.parse('1.2.0'), :==, ::Versionomy.parse('1.2.0', :semver))
+        assert_operator(::Versionomy.parse('1.2b3'), :==, ::Versionomy.parse('1.2.0beta3', :semver))
+      end
+
+
+      # Test inequality comparisons between standard and rubygems
+
+      def test_standard_to_semver_inequality_comparison
+        assert_operator(::Versionomy.parse('1.2.4'), :>, ::Versionomy.parse('1.2.3', :semver))
+        assert_operator(::Versionomy.parse('1.2b2'), :<, ::Versionomy.parse('1.2.0beta3', :semver))
+        assert_operator(::Versionomy.parse('1.2b2'), :<, ::Versionomy.parse('1.2.0', :semver))
+      end
+
+
+    end
+
+  end
+end
diff --git a/test/tc_standard_basic.rb b/test/tc_standard_basic.rb
new file mode 100644
index 0000000..4e4f223
--- /dev/null
+++ b/test/tc_standard_basic.rb
@@ -0,0 +1,175 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy basic tests on standard schema
+#
+# This file contains tests for the basic use cases on the standard schema
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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.
+# -----------------------------------------------------------------------------
+
+
+require 'test/unit'
+require 'versionomy'
+
+
+module Versionomy
+  module Tests  # :nodoc:
+
+    class TestStandardBasic < ::Test::Unit::TestCase  # :nodoc:
+
+
+      # Test the default version value.
+
+      def test_default_value
+        value_ = ::Versionomy.create
+        assert_equal(1, value_.major)
+        assert_equal(0, value_.minor)
+        assert_equal(0, value_.tiny)
+        assert_equal(0, value_.tiny2)
+        assert_equal(:final, value_.release_type)
+        assert_equal(0, value_.patchlevel)
+        assert_equal(0, value_.patchlevel_minor)
+      end
+
+
+      # Test an arbitrary release value.
+
+      def test_release_value_1
+        value_ = ::Versionomy.create(:major => 1, :tiny => 4, :tiny2 => 2, :patchlevel => 5)
+        assert_equal(1, value_.major)
+        assert_equal(0, value_.minor)
+        assert_equal(4, value_.tiny)
+        assert_equal(2, value_.tiny2)
+        assert_equal(:final, value_.release_type)
+        assert_equal(5, value_.patchlevel)
+        assert_equal(0, value_.patchlevel_minor)
+        assert_equal(false, value_.has_field?(:prerelase_version))
+        assert_equal(false, value_.has_field?(:prerelase_minor))
+      end
+
+
+      # Test an arbitrary release value.
+
+      def test_release_value_2
+        value_ = ::Versionomy.create(:major => 0, :minor => 3)
+        assert_equal(0, value_.major)
+        assert_equal(3, value_.minor)
+        assert_equal(0, value_.tiny)
+        assert_equal(0, value_.tiny2)
+        assert_equal(:final, value_.release_type)
+        assert_equal(0, value_.patchlevel)
+        assert_equal(0, value_.patchlevel_minor)
+        assert_equal(false, value_.has_field?(:prerelase_version))
+        assert_equal(false, value_.has_field?(:prerelase_minor))
+      end
+
+
+      # Test an arbitrary preview value.
+
+      def test_preview_value_1
+        value_ = ::Versionomy.create(:major => 2, :minor => 3, :release_type => :preview, :preview_version => 3)
+        assert_equal(2, value_.major)
+        assert_equal(3, value_.minor)
+        assert_equal(0, value_.tiny)
+        assert_equal(0, value_.tiny2)
+        assert_equal(:preview, value_.release_type)
+        assert_equal(3, value_.preview_version)
+        assert_equal(0, value_.preview_minor)
+        assert_equal(false, value_.has_field?(:patchlevel))
+        assert_equal(false, value_.has_field?(:patchlevel_minor))
+      end
+
+
+      # Test an arbitrary preview value.
+
+      def test_preview_value_2
+        value_ = ::Versionomy.create(:major => 2, :minor => 3, :release_type => :preview)
+        assert_equal(2, value_.major)
+        assert_equal(3, value_.minor)
+        assert_equal(0, value_.tiny)
+        assert_equal(0, value_.tiny2)
+        assert_equal(:preview, value_.release_type)
+        assert_equal(1, value_.preview_version)
+        assert_equal(0, value_.preview_minor)
+        assert_equal(false, value_.has_field?(:patchlevel))
+        assert_equal(false, value_.has_field?(:patchlevel_minor))
+      end
+
+
+      # Test an arbitrary beta value.
+
+      def test_beta_value
+        value_ = ::Versionomy.create(:major => 2, :tiny => 1, :release_type => :beta, :beta_version => 3)
+        assert_equal(2, value_.major)
+        assert_equal(0, value_.minor)
+        assert_equal(1, value_.tiny)
+        assert_equal(0, value_.tiny2)
+        assert_equal(:beta, value_.release_type)
+        assert_equal(3, value_.beta_version)
+        assert_equal(0, value_.beta_minor)
+        assert_equal(false, value_.has_field?(:prerelase_version))
+        assert_equal(false, value_.has_field?(:prerelase_minor))
+        assert_equal(false, value_.has_field?(:patchlevel))
+        assert_equal(false, value_.has_field?(:patchlevel_minor))
+      end
+
+
+      # Test specifying fields by index.
+
+      def test_field_get_index
+        value_ = ::Versionomy.create(:major => 2, :tiny => 1, :release_type => :beta, :beta_version => 3)
+        assert_equal(2, value_[0])
+        assert_equal(0, value_[1])
+        assert_equal(1, value_[2])
+        assert_equal(0, value_[3])
+        assert_equal(:beta, value_[4])
+        assert_equal(3, value_[5])
+        assert_equal(0, value_[6])
+      end
+
+
+      # Test specifying fields by name.
+
+      def test_field_get_name
+        value_ = ::Versionomy.create(:major => 2, :tiny => 1, :release_type => :beta, :beta_version => 3)
+        assert_equal(2, value_[:major])
+        assert_equal(0, value_[:minor])
+        assert_equal(1, value_[:tiny])
+        assert_equal(0, value_[:tiny2])
+        assert_equal(:beta, value_[:release_type])
+        assert_equal(3, value_[:beta_version])
+        assert_equal(0, value_[:beta_minor])
+      end
+
+
+    end
+
+  end
+end
diff --git a/test/tc_standard_bump.rb b/test/tc_standard_bump.rb
new file mode 100644
index 0000000..a63cef5
--- /dev/null
+++ b/test/tc_standard_bump.rb
@@ -0,0 +1,171 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy bump tests on standard schema
+#
+# This file contains tests for the bump function on the standard schema
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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.
+# -----------------------------------------------------------------------------
+
+
+require 'test/unit'
+require 'versionomy'
+
+
+module Versionomy
+  module Tests  # :nodoc:
+
+    class TestStandardBump < ::Test::Unit::TestCase  # :nodoc:
+
+
+      # Test bumping a minor patchlevel.
+
+      def test_bump_patchlevel_minor
+        value_ = ::Versionomy.create(:major => 2, :tiny => 1, :patchlevel => 3, :patchlevel_minor => 0)
+        value_ = value_.bump(:patchlevel_minor)
+        assert_equal(2, value_.major)
+        assert_equal(0, value_.minor)
+        assert_equal(1, value_.tiny)
+        assert_equal(0, value_.tiny2)
+        assert_equal(:final, value_.release_type)
+        assert_equal(3, value_.patchlevel)
+        assert_equal(1, value_.patchlevel_minor)
+      end
+
+
+      # Test bumping a major patchlevel.
+
+      def test_bump_patchlevel
+        value_ = ::Versionomy.create(:major => 2, :tiny => 1, :patchlevel => 3, :patchlevel_minor => 1)
+        value_ = value_.bump(:patchlevel)
+        assert_equal(2, value_.major)
+        assert_equal(0, value_.minor)
+        assert_equal(1, value_.tiny)
+        assert_equal(0, value_.tiny2)
+        assert_equal(:final, value_.release_type)
+        assert_equal(4, value_.patchlevel)
+        assert_equal(0, value_.patchlevel_minor)
+      end
+
+
+      # Test bumping release type preview.
+
+      def test_bump_preview_to_release
+        value_ = ::Versionomy.create(:major => 2, :tiny => 1, :release_type => :preview)
+        value_ = value_.bump(:release_type)
+        assert_equal(2, value_.major)
+        assert_equal(0, value_.minor)
+        assert_equal(1, value_.tiny)
+        assert_equal(0, value_.tiny2)
+        assert_equal(:final, value_.release_type)
+        assert_equal(0, value_.patchlevel)
+        assert_equal(0, value_.patchlevel_minor)
+      end
+
+
+      # Test bumping release type development.
+
+      def test_bump_development_to_alpha
+        value_ = ::Versionomy.create(:major => 2, :tiny => 1, :release_type => :development, :development_version => 7)
+        value_ = value_.bump(:release_type)
+        assert_equal(2, value_.major)
+        assert_equal(0, value_.minor)
+        assert_equal(1, value_.tiny)
+        assert_equal(0, value_.tiny2)
+        assert_equal(:alpha, value_.release_type)
+        assert_equal(1, value_.alpha_version)
+        assert_equal(0, value_.alpha_minor)
+      end
+
+
+      # Test bumping release type alpha.
+
+      def test_bump_alpha_to_beta
+        value_ = ::Versionomy.create(:major => 2, :tiny => 1, :release_type => :alpha)
+        value_ = value_.bump(:release_type)
+        assert_equal(2, value_.major)
+        assert_equal(0, value_.minor)
+        assert_equal(1, value_.tiny)
+        assert_equal(0, value_.tiny2)
+        assert_equal(:beta, value_.release_type)
+        assert_equal(1, value_.beta_version)
+        assert_equal(0, value_.beta_minor)
+      end
+
+
+      # Test bumping release type release_candidate.
+
+      def test_bump_release_candidate_to_release
+        value_ = ::Versionomy.create(:major => 2, :tiny => 1, :release_type => :release_candidate, :release_candidate_version => 2)
+        value_ = value_.bump(:release_type)
+        assert_equal(2, value_.major)
+        assert_equal(0, value_.minor)
+        assert_equal(1, value_.tiny)
+        assert_equal(0, value_.tiny2)
+        assert_equal(:final, value_.release_type)
+        assert_equal(0, value_.patchlevel)
+        assert_equal(0, value_.patchlevel_minor)
+      end
+
+
+      # Test bumping tiny.
+
+      def test_bump_tiny
+        value_ = ::Versionomy.create(:major => 2, :tiny => 1, :tiny2 => 3, :release_type => :release_candidate, :release_candidate_version => 2)
+        value_ = value_.bump(:tiny)
+        assert_equal(2, value_.major)
+        assert_equal(0, value_.minor)
+        assert_equal(2, value_.tiny)
+        assert_equal(0, value_.tiny2)
+        assert_equal(:final, value_.release_type)
+        assert_equal(0, value_.patchlevel)
+        assert_equal(0, value_.patchlevel_minor)
+      end
+
+
+      # Test bumping major.
+
+      def test_bump_major
+        value_ = ::Versionomy.create(:major => 2, :tiny => 1, :tiny2 => 3, :release_type => :release_candidate, :release_candidate_version => 2)
+        value_ = value_.bump(:major)
+        assert_equal(3, value_.major)
+        assert_equal(0, value_.minor)
+        assert_equal(0, value_.tiny)
+        assert_equal(0, value_.tiny2)
+        assert_equal(:final, value_.release_type)
+        assert_equal(0, value_.patchlevel)
+        assert_equal(0, value_.patchlevel_minor)
+      end
+
+
+    end
+
+  end
+end
diff --git a/test/tc_standard_change.rb b/test/tc_standard_change.rb
new file mode 100644
index 0000000..7ca04ea
--- /dev/null
+++ b/test/tc_standard_change.rb
@@ -0,0 +1,98 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy change tests on standard schema
+#
+# This file contains tests for the change function on the standard schema
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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.
+# -----------------------------------------------------------------------------
+
+
+require 'test/unit'
+require 'versionomy'
+
+
+module Versionomy
+  module Tests  # :nodoc:
+
+    class TestStandardChange < ::Test::Unit::TestCase  # :nodoc:
+
+
+      # Test with a changed tiny
+
+      def test_change_tiny
+        value_ = ::Versionomy.create(:major => 2, :tiny => 1, :tiny2 => 3, :release_type => :release_candidate, :release_candidate_version => 2)
+        value_ = value_.change(:tiny => 4)
+        assert_equal(2, value_.major)
+        assert_equal(0, value_.minor)
+        assert_equal(4, value_.tiny)
+        assert_equal(3, value_.tiny2)
+        assert_equal(:release_candidate, value_.release_type)
+        assert_equal(2, value_.release_candidate_version)
+        assert_equal(0, value_.release_candidate_minor)
+      end
+
+
+      # Test with several changed fields
+
+      def test_change_several
+        value_ = ::Versionomy.create(:major => 2, :tiny => 1, :tiny2 => 3, :release_type => :release_candidate, :release_candidate_version => 2)
+        value_ = value_.change(:tiny => 4, :release_candidate_version => 5)
+        assert_equal(2, value_.major)
+        assert_equal(0, value_.minor)
+        assert_equal(4, value_.tiny)
+        assert_equal(3, value_.tiny2)
+        assert_equal(:release_candidate, value_.release_type)
+        assert_equal(5, value_.release_candidate_version)
+        assert_equal(0, value_.release_candidate_minor)
+      end
+
+
+      # Test with a changed release type
+
+      def test_change_release_type
+        value_ = ::Versionomy.create(:major => 2, :tiny => 1, :tiny2 => 3, :release_type => :release_candidate, :release_candidate_version => 2)
+        value_ = value_.change(:release_type => :beta)
+        assert_equal(2, value_.major)
+        assert_equal(0, value_.minor)
+        assert_equal(1, value_.tiny)
+        assert_equal(3, value_.tiny2)
+        assert_equal(:beta, value_.release_type)
+        assert_equal(1, value_.beta_version)
+        assert_equal(0, value_.beta_minor)
+        assert_equal(false, value_.has_field?(:release_candidate_version))
+        assert_equal(false, value_.has_field?(:release_candidate_minor))
+      end
+
+
+    end
+
+  end
+end
diff --git a/test/tc_standard_comparison.rb b/test/tc_standard_comparison.rb
new file mode 100644
index 0000000..fcff322
--- /dev/null
+++ b/test/tc_standard_comparison.rb
@@ -0,0 +1,142 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy comparison tests on standard schema
+#
+# This file contains tests for comparisons on the standard schema
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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.
+# -----------------------------------------------------------------------------
+
+
+require 'test/unit'
+require 'versionomy'
+
+
+module Versionomy
+  module Tests  # :nodoc:
+
+    class TestStandardComparison < ::Test::Unit::TestCase  # :nodoc:
+
+
+      # Test comparisons with difference in major.
+
+      def test_comparison_major
+        value1_ = ::Versionomy.create(:major => 2, :tiny => 1, :tiny2 => 3, :release_type => :release_candidate, :release_candidate_version => 2)
+        value2_ = ::Versionomy.create(:major => 3, :release_type => :alpha)
+        assert(value2_ > value1_)
+      end
+
+
+      # Test comparisons with difference in minor.
+
+      def test_comparison_minor
+        value1_ = ::Versionomy.create(:major => 2, :tiny => 1, :tiny2 => 3, :release_type => :release_candidate, :release_candidate_version => 2)
+        value2_ = ::Versionomy.create(:major => 2, :minor => 1, :release_type => :alpha)
+        assert(value2_ > value1_)
+      end
+
+
+      # Test comparisons with difference in release type.
+
+      def test_comparison_release_type
+        value1_ = ::Versionomy.create(:major => 2, :release_type => :alpha, :alpha_version => 5)
+        value2_ = ::Versionomy.create(:major => 2, :release_type => :release_candidate, :release_candidate_version => 2)
+        assert(value2_ > value1_)
+      end
+
+
+      # Test equality for a simple case.
+
+      def test_equality_simple
+        value1_ = ::Versionomy.create(:major => 2, :minor => 0, :release_type => :alpha, :alpha_version => 5)
+        value2_ = ::Versionomy.create(:major => 2, :release_type => :alpha, :alpha_version => 5)
+        assert_equal(value2_, value1_)
+        assert_equal(value2_.hash, value1_.hash)
+      end
+
+
+      # Test equality from parsed values.
+
+      def test_equality_parsed
+        value1_ = ::Versionomy.parse("1.8.7p72")
+        value2_ = ::Versionomy.parse("1.8.7.0-72.0")
+        assert_equal(value2_, value1_)
+        assert_equal(value2_.hash, value1_.hash)
+      end
+
+
+      # Test non-equality from parsed values.
+
+      def test_nonequality_parsed
+        value1_ = ::Versionomy.parse("1.8.7b7")
+        value2_ = ::Versionomy.parse("1.8.7a7")
+        assert_not_equal(value2_, value1_)
+        assert_not_equal(value2_.hash, value1_.hash)
+      end
+
+
+      # Test equality with string.
+
+      def test_equality_string
+        value1_ = ::Versionomy.parse("1.8.7p72")
+        assert_operator(value1_, :==, "1.8.7p72")
+        assert_operator(value1_, :==, "1.8.7.0-72.0")
+      end
+
+
+      # Test comparison with string.
+
+      def test_comparison_string
+        value1_ = ::Versionomy.parse("1.8.7p72")
+        assert_operator(value1_, :<, "1.8.7p73")
+        assert_operator(value1_, :<, "1.8.8pre1")
+        assert_operator(value1_, :>, "1.8.7p71")
+        assert_operator(value1_, :>, "1.8.7rc2")
+        assert_operator(value1_, :>, "1.8.7.0")
+      end
+
+
+      # Test sorting.
+
+      def test_sort
+        value1_ = ::Versionomy.parse("1.8.7p73")
+        value2_ = ::Versionomy.parse("1.8.7p72")
+        value3_ = ::Versionomy.parse("1.8.8pre1")
+        value4_ = ::Versionomy.parse("1.8.7.0")
+        value5_ = ::Versionomy.parse("1.8.7rc2")
+        assert_equal([value5_, value4_, value2_, value1_, value3_],
+                     [value1_, value2_, value3_, value4_, value5_].sort)
+      end
+
+
+    end
+
+  end
+end
diff --git a/test/tc_standard_misc.rb b/test/tc_standard_misc.rb
new file mode 100644
index 0000000..f47f86d
--- /dev/null
+++ b/test/tc_standard_misc.rb
@@ -0,0 +1,95 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy basic tests on standard schema
+#
+# This file contains tests for the basic use cases on the standard schema
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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.
+# -----------------------------------------------------------------------------
+
+
+require 'test/unit'
+require 'versionomy'
+
+
+module Versionomy
+  module Tests  # :nodoc:
+
+    class TestStandardMisc < ::Test::Unit::TestCase  # :nodoc:
+
+
+      # Test "prerelase?" custom method
+
+      def test_method_prereleasep
+        value_ = ::Versionomy.create(:major => 2, :tiny => 1, :release_type => :beta, :beta_version => 3)
+        assert_equal(true, value_.prerelease?)
+        value_ = ::Versionomy.create(:major => 2, :tiny => 1, :release_type => :final, :patchlevel => 1)
+        assert_equal(false, value_.prerelease?)
+        value_ = ::Versionomy.create(:major => 2, :tiny => 1)
+        assert_equal(false, value_.prerelease?)
+      end
+
+
+      # Test "relase" custom method
+
+      def test_method_release
+        value_ = ::Versionomy.create(:major => 1, :minor => 9, :tiny => 2, :release_type => :alpha, :alpha_version => 4)
+        value2_ = value_.release
+        assert_equal([1, 9, 2, 0, :final, 0, 0], value2_.values_array)
+        value_ = ::Versionomy.create(:major => 1, :minor => 9, :tiny => 2)
+        value2_ = value_.release
+        assert_equal(value_, value2_)
+      end
+
+
+      # Test marshalling
+
+      def test_marshal
+        value_ = ::Versionomy.create(:major => 1, :minor => 9, :tiny => 2, :release_type => :alpha, :alpha_version => 4)
+        str_ = ::Marshal.dump(value_)
+        value2_ = ::Marshal.load(str_)
+        assert_equal(value_, value2_)
+      end
+
+
+      # Test YAML
+
+      def test_yaml
+        value_ = ::Versionomy.create(:major => 1, :minor => 9, :tiny => 2, :release_type => :alpha, :alpha_version => 4)
+        str_ = ::YAML.dump(value_)
+        value2_ = ::YAML.load(str_)
+        assert_equal(value_, value2_)
+      end
+
+
+    end
+
+  end
+end
diff --git a/test/tc_standard_parse.rb b/test/tc_standard_parse.rb
new file mode 100644
index 0000000..ad6570a
--- /dev/null
+++ b/test/tc_standard_parse.rb
@@ -0,0 +1,388 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy parsing tests on standard schema
+#
+# This file contains tests for parsing on the standard schema
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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.
+# -----------------------------------------------------------------------------
+
+
+require 'test/unit'
+require 'versionomy'
+
+
+module Versionomy
+  module Tests  # :nodoc:
+
+    class TestStandardParse < ::Test::Unit::TestCase  # :nodoc:
+
+
+      # Test parsing full.
+
+      def test_parsing_full_release
+        value_ = ::Versionomy.parse('2.0.1.1-4.6')
+        assert_equal(2, value_.major)
+        assert_equal(0, value_.minor)
+        assert_equal(1, value_.tiny)
+        assert_equal(1, value_.tiny2)
+        assert_equal(:final, value_.release_type)
+        assert_equal(4, value_.patchlevel)
+        assert_equal(6, value_.patchlevel_minor)
+        assert_equal('2.0.1.1-4.6', value_.unparse)
+      end
+
+
+      # Test parsing abbreviated.
+
+      def test_parsing_abbrev_release
+        value_ = ::Versionomy.parse('2.0.1')
+        assert_equal(2, value_.major)
+        assert_equal(0, value_.minor)
+        assert_equal(1, value_.tiny)
+        assert_equal(0, value_.tiny2)
+        assert_equal(:final, value_.release_type)
+        assert_equal(0, value_.patchlevel)
+        assert_equal(0, value_.patchlevel_minor)
+        assert_equal('2.0.1.0-0.0', value_.unparse(:required_fields => [:minor, :tiny, :tiny2, :patchlevel, :patchlevel_minor]))
+        assert_equal('2.0.1-0', value_.unparse(:required_fields => [:minor, :patchlevel]))
+        assert_equal('2.0.1', value_.unparse)
+      end
+
+
+      # Test parsing with trailing zeros.
+
+      def test_parsing_trailing_zeros
+        value_ = ::Versionomy.parse('2.0.0')
+        assert_equal(2, value_.major)
+        assert_equal(0, value_.minor)
+        assert_equal(0, value_.tiny)
+        assert_equal(0, value_.tiny2)
+        assert_equal(:final, value_.release_type)
+        assert_equal(0, value_.patchlevel)
+        assert_equal(0, value_.patchlevel_minor)
+        assert_equal('2.0.0', value_.unparse)
+      end
+
+
+      # Test parsing with leading zeros on a field.
+
+      def test_parsing_field_leading_zeros
+        value_ = ::Versionomy.parse('2.09')
+        assert_equal(2, value_.major)
+        assert_equal(9, value_.minor)
+        assert_equal(0, value_.tiny)
+        assert_equal('2.09', value_.unparse)
+        value_ = value_.bump(:minor)
+        assert_equal(10, value_.minor)
+        assert_equal('2.10', value_.unparse)
+        value_ = value_.change(:minor => 123)
+        assert_equal(123, value_.minor)
+        assert_equal('2.123', value_.unparse)
+        value_ = value_.change(:minor => 4)
+        assert_equal(4, value_.minor)
+        assert_equal('2.04', value_.unparse)
+        value_ = ::Versionomy.parse('2.00')
+        assert_equal(0, value_.minor)
+        assert_equal('2.00', value_.unparse)
+      end
+
+
+      # Test unparsing with a minimum width.
+
+      def test_unparsing_minimum_width_field
+        value_ = ::Versionomy.parse('2.0')
+        assert_equal('2.0', value_.unparse)
+        assert_equal('2.00', value_.unparse(:minor_width => 2))
+        assert_equal('02.0', value_.unparse(:major_width => 2))
+        value_ = value_.bump(:minor)
+        assert_equal('2.1', value_.unparse)
+        assert_equal('2.01', value_.unparse(:minor_width => 2))
+        value_ = value_.change(:minor => 12)
+        assert_equal('2.12', value_.unparse)
+        assert_equal('2.12', value_.unparse(:minor_width => 1))
+      end
+
+
+      # Test parsing major version only.
+
+      def test_parsing_major_only
+        value_ = ::Versionomy.parse('2')
+        assert_equal(2, value_.major)
+        assert_equal(0, value_.minor)
+        assert_equal(0, value_.tiny)
+        assert_equal(0, value_.tiny2)
+        assert_equal(:final, value_.release_type)
+        assert_equal(0, value_.patchlevel)
+        assert_equal(0, value_.patchlevel_minor)
+        assert_equal('2', value_.unparse)
+      end
+
+
+      # Test parsing preview.
+
+      def test_parsing_preview
+        value_ = ::Versionomy.parse('2.0.1pre4')
+        assert_equal(2, value_.major)
+        assert_equal(0, value_.minor)
+        assert_equal(1, value_.tiny)
+        assert_equal(0, value_.tiny2)
+        assert_equal(:preview, value_.release_type)
+        assert_equal(4, value_.preview_version)
+        assert_equal(0, value_.preview_minor)
+        assert_equal('2.0.1pre4', value_.unparse)
+        assert_equal('2.0.1pre4.0', value_.unparse(:required_fields => [:preview_minor]))
+      end
+
+
+      # Test parsing alpha.
+
+      def test_parsing_alpha
+        value_ = ::Versionomy.parse('2.0.1a4.1')
+        assert_equal(2, value_.major)
+        assert_equal(0, value_.minor)
+        assert_equal(1, value_.tiny)
+        assert_equal(0, value_.tiny2)
+        assert_equal(:alpha, value_.release_type)
+        assert_equal(4, value_.alpha_version)
+        assert_equal(1, value_.alpha_minor)
+        assert_equal('2.0.1a4.1', value_.unparse)
+        assert_equal('2.0.1a4.1', value_.unparse(:optional_fields => [:alpha_minor]))
+      end
+
+
+      # Test parsing beta.
+
+      def test_parsing_beta
+        value_ = ::Versionomy.parse('2.52.1b4.0')
+        assert_equal(2, value_.major)
+        assert_equal(52, value_.minor)
+        assert_equal(1, value_.tiny)
+        assert_equal(0, value_.tiny2)
+        assert_equal(:beta, value_.release_type)
+        assert_equal(4, value_.beta_version)
+        assert_equal(0, value_.beta_minor)
+        assert_equal('2.52.1b4.0', value_.unparse)
+        assert_equal('2.52.1b4', value_.unparse(:optional_fields => [:beta_minor]))
+      end
+
+
+      # Test parsing beta alternates
+
+      def test_parsing_beta_alternates
+        assert_equal(::Versionomy.parse('2.52.1 beta4'), '2.52.1b4')
+        assert_equal(::Versionomy.parse('2.52.1-b4'), '2.52.1b4')
+        assert_equal(::Versionomy.parse('2.52.1_b4'), '2.52.1b4')
+        assert_equal(::Versionomy.parse('2.52.1.b4'), '2.52.1b4')
+        assert_equal(::Versionomy.parse('2.52.1B4'), '2.52.1b4')
+        assert_equal(::Versionomy.parse('2.52.1BETA4'), '2.52.1b4')
+        assert_equal(::Versionomy.parse('2.52.1 Beta4'), '2.52.1b4')
+        assert_equal(::Versionomy.parse('2.52.1 eta4', :extra_characters => :ignore), '2.52.1')
+        assert_equal(::Versionomy.parse('2.52.1 Beta'), '2.52.1b0')
+      end
+
+
+      # Test parsing release candidate.
+
+      def test_parsing_release_candidate
+        value_ = ::Versionomy.parse('0.2rc0')
+        assert_equal(0, value_.major)
+        assert_equal(2, value_.minor)
+        assert_equal(0, value_.tiny)
+        assert_equal(0, value_.tiny2)
+        assert_equal(:release_candidate, value_.release_type)
+        assert_equal(0, value_.release_candidate_version)
+        assert_equal(0, value_.release_candidate_minor)
+        assert_equal('0.2rc0', value_.unparse)
+        assert_equal('0.2rc0.0', value_.unparse(:required_fields => [:release_candidate_minor]))
+        assert_equal('0.2rc', value_.unparse(:optional_fields => [:release_candidate_version]))
+      end
+
+
+      # Test parsing release candidate changing to other prerelease.
+      # Ensures that :short style takes precedence over :long for parsing "rc".
+
+      def test_parsing_release_candidate_type_change
+        value_ = ::Versionomy.parse('0.2rc1')
+        assert_equal(:release_candidate, value_.release_type)
+        assert_equal(1, value_.release_candidate_version)
+        assert_equal('0.2rc1', value_.unparse)
+        value_ = value_.change(:release_type => :beta)
+        assert_equal(:beta, value_.release_type)
+        assert_equal(1, value_.beta_version)
+        assert_equal('0.2b1', value_.unparse)
+        value_ = value_.change({:release_type => :beta}, :release_type_style => :long)
+        assert_equal(:beta, value_.release_type)
+        assert_equal(1, value_.beta_version)
+        assert_equal('0.2beta1', value_.unparse)
+      end
+
+
+      # Test parsing forms without a prerelease version
+
+      def test_parsing_without_prerelease_version
+        value_ = ::Versionomy.parse('1.9.2dev')
+        assert_equal(value_.release_type, :development)
+        assert_equal(value_.development_version, 0)
+        assert_equal('1.9.2dev', value_.to_s)
+        value_ = value_.bump(:development_version)
+        assert_equal('1.9.2dev1', value_.to_s)
+      end
+
+
+      # Test parsing forms without a prerelease version.
+      # Ensures that :development_version prefers to be required.
+
+      def test_unparsing_prerelease_version_0
+        value_ = ::Versionomy.parse('1.9.2dev1')
+        assert_equal(value_.release_type, :development)
+        assert_equal(value_.development_version, 1)
+        assert_equal('1.9.2dev1', value_.to_s)
+        value2_ = value_.change(:development_version => 0)
+        assert_equal('1.9.2dev0', value2_.to_s)
+        value2_ = value_.change({:development_version => 0}, :optional_fields => [:development_version])
+        assert_equal('1.9.2dev', value2_.to_s)
+      end
+
+
+      # Test unparsing a value that requires lookback.
+
+      def test_unparsing_with_lookback
+        value_ = ::Versionomy.parse('2.0')
+        value2_ = value_.change(:tiny2 => 1)
+        assert_equal(1, value2_.tiny2)
+        assert_equal('2.0.0.1', value2_.unparse)
+        value3_ = value2_.change(:tiny2 => 0)
+        assert_equal(0, value3_.tiny2)
+        assert_equal('2.0', value3_.unparse)
+      end
+
+
+      # Test delimiter changes in a multi-form field.
+
+      def test_multi_form_delimiter_changes
+        value_ = ::Versionomy.parse('2.0 preview 1')
+        assert_equal('2.0 preview 1', value_.to_s)
+        value2_ = value_.change(:release_type => :final)
+        assert_equal('2.0', value2_.to_s)
+        value3_ = value2_.change(:release_type => :preview, :preview_version => 1)
+        assert_equal('2.0 preview 1', value3_.to_s)
+      end
+
+
+      # Test different patchlevel separators.
+
+      def test_patchlevel_separators
+        expected_ = [1,9,1,0,:final,243,0]
+        assert_equal(expected_, ::Versionomy.parse('1.9.1-p243').values_array)
+        assert_equal(expected_, ::Versionomy.parse('1.9.1_p243').values_array)
+        assert_equal(expected_, ::Versionomy.parse('1.9.1_u243').values_array)
+        assert_equal(expected_, ::Versionomy.parse('1.9.1p243').values_array)
+        assert_equal(expected_, ::Versionomy.parse('1.9.1.p243').values_array)
+        assert_equal(expected_, ::Versionomy.parse('1.9.1 p243').values_array)
+        assert_equal(expected_, ::Versionomy.parse('1.9.1-243').values_array)
+        assert_equal(expected_, ::Versionomy.parse('1.9.1_243').values_array)
+      end
+
+
+      # Test alphabetic patchlevels.
+      # In particular, make sure the parser can distinguish between these
+      # and the markers for prereleases.
+
+      def test_patchlevel_alphabetic
+        value_ = ::Versionomy.parse('1.9a')
+        assert_equal([1, 9, 0, 0, :final, 1, 0], value_.values_array)
+        assert_equal('1.9a', value_.to_s)
+        value_ = ::Versionomy.parse('1.9b')
+        assert_equal([1, 9, 0, 0, :final, 2, 0], value_.values_array)
+        assert_equal('1.9b', value_.to_s)
+        value_ = ::Versionomy.parse('1.9d')
+        assert_equal([1, 9, 0, 0, :final, 4, 0], value_.values_array)
+        assert_equal('1.9d', value_.to_s)
+        value_ = ::Versionomy.parse('1.9p')
+        assert_equal([1, 9, 0, 0, :final, 16, 0], value_.values_array)
+        assert_equal('1.9p', value_.to_s)
+        value_ = ::Versionomy.parse('1.9r')
+        assert_equal([1, 9, 0, 0, :final, 18, 0], value_.values_array)
+        assert_equal('1.9r', value_.to_s)
+        value_ = ::Versionomy.parse('1.9u')
+        assert_equal([1, 9, 0, 0, :final, 21, 0], value_.values_array)
+        assert_equal('1.9u', value_.to_s)
+      end
+
+
+      # Test setting delimiters on unparse, including testing for illegal delimiters
+
+      def test_unparse_with_custom_delimiters
+        value_ = ::Versionomy.parse('1.2b3')
+        assert_equal('1.2.b.3', value_.unparse(:release_type_delim => '.', :beta_version_delim => '.'))
+        assert_equal('1.2b3', value_.unparse(:release_type_delim => '=', :beta_version_delim => '*'))
+        value_ = ::Versionomy.parse('1.2-4')
+        assert_equal('1.2-4', value_.unparse(:release_type_delim => '.'))
+      end
+
+
+      # Test java version formats
+
+      def test_java_formats
+        value_ = ::Versionomy.parse('1.6.0_17')
+        assert_equal([1, 6, 0, 0, :final, 17, 0], value_.values_array)
+        assert_equal('1.6.0_17', value_.to_s)
+        value_ = ::Versionomy.parse('6u17')
+        assert_equal([6, 0, 0, 0, :final, 17, 0], value_.values_array)
+        assert_equal('6u17', value_.to_s)
+      end
+
+
+      # Test formats prefixed with "v"
+
+      def test_v_prefix
+        value_ = ::Versionomy.parse('v1.2')
+        assert_equal([1, 2, 0, 0, :final, 0, 0], value_.values_array)
+        assert_equal('v1.2', value_.to_s)
+        value_ = ::Versionomy.parse('V 2.3')
+        assert_equal([2, 3, 0, 0, :final, 0, 0], value_.values_array)
+        assert_equal('V 2.3', value_.to_s)
+      end
+
+
+      # Test parse errors
+
+      def test_parsing_errors
+        assert_raise(::Versionomy::Errors::ParseError) do
+          ::Versionomy.parse('2.52.1 eta4')
+        end
+      end
+
+
+    end
+
+  end
+end
diff --git a/test/tc_standard_reset.rb b/test/tc_standard_reset.rb
new file mode 100644
index 0000000..268d14b
--- /dev/null
+++ b/test/tc_standard_reset.rb
@@ -0,0 +1,105 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy bump tests on standard schema
+#
+# This file contains tests for the bump function on the standard schema
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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.
+# -----------------------------------------------------------------------------
+
+
+require 'test/unit'
+require 'versionomy'
+
+
+module Versionomy
+  module Tests  # :nodoc:
+
+    class TestStandardReset < ::Test::Unit::TestCase  # :nodoc:
+
+
+      # Test resetting a minor patchlevel.
+
+      def test_reset_patchlevel_minor
+        value_ = ::Versionomy.create(:major => 2, :tiny => 1, :patchlevel => 3, :patchlevel_minor => 1)
+        value_ = value_.reset(:patchlevel_minor)
+        assert_equal([2,0,1,0,:final,3,0], value_.values_array)
+      end
+
+
+      # Test resetting a major patchlevel.
+
+      def test_reset_patchlevel
+        value_ = ::Versionomy.create(:major => 2, :tiny => 1, :patchlevel => 3, :patchlevel_minor => 1)
+        value_ = value_.reset(:patchlevel)
+        assert_equal([2,0,1,0,:final,0,0], value_.values_array)
+      end
+
+
+      # Test resetting a beta release type.
+
+      def test_reset_beta_release_type
+        value_ = ::Versionomy.create(:major => 2, :tiny => 1, :release_type => :beta, :beta_version => 2)
+        value_ = value_.reset(:release_type)
+        assert_equal([2,0,1,0,:final,0,0], value_.values_array)
+      end
+
+
+      # Test resetting a final release type.
+
+      def test_reset_final_release_type
+        value_ = ::Versionomy.create(:major => 2, :tiny => 1, :patchlevel => 2)
+        value_ = value_.reset(:release_type)
+        assert_equal([2,0,1,0,:final,0,0], value_.values_array)
+      end
+
+
+      # Test resetting tiny.
+
+      def test_reset_tiny
+        value_ = ::Versionomy.create(:major => 2, :tiny => 1, :tiny2 => 3, :release_type => :release_candidate, :release_candidate_version => 2)
+        value_ = value_.reset(:tiny)
+        assert_equal([2,0,0,0,:final,0,0], value_.values_array)
+      end
+
+
+      # Test resetting major.
+
+      def test_reset_major
+        value_ = ::Versionomy.create(:major => 2, :tiny => 1, :tiny2 => 3, :release_type => :release_candidate, :release_candidate_version => 2)
+        value_ = value_.reset(:major)
+        assert_equal([1,0,0,0,:final,0,0], value_.values_array)
+      end
+
+
+    end
+
+  end
+end
diff --git a/test/tc_version_of.rb b/test/tc_version_of.rb
new file mode 100644
index 0000000..2ae7cfc
--- /dev/null
+++ b/test/tc_version_of.rb
@@ -0,0 +1,81 @@
+# -----------------------------------------------------------------------------
+#
+# Versionomy basic tests on standard schema
+#
+# This file contains tests for the basic use cases on the standard schema
+#
+# -----------------------------------------------------------------------------
+# Copyright 2008-2012 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.
+# -----------------------------------------------------------------------------
+
+
+require 'test/unit'
+require 'versionomy'
+
+
+module Versionomy
+  module Tests  # :nodoc:
+
+    class TestVersionOf < ::Test::Unit::TestCase  # :nodoc:
+
+
+      # Gems to test if we can
+      GEM_LIST = {
+        'activerecord' => {:require => 'active_record', :module_name => 'ActiveRecord'},
+        'blockenspiel' => {:module_name => 'Blockenspiel'},
+        'bundler' => {:module_name => 'Bundler'},
+        'erubis' => {:module_name => 'Erubis'},
+      }
+
+
+      # Engine that tests each gem if it's installed
+      zero_ = ::Versionomy.create(:major => 0)
+      GEM_LIST.each do |name_, data_|
+        unless data_.kind_of?(::Hash)
+          data_ = {:module_name => data_}
+        end
+        begin
+          gem name_
+          require data_[:require] || name_
+        rescue ::LoadError
+          next
+        end
+        define_method("test_gem_#{name_}") do
+          mod_ = eval(data_[:module_name])
+          value_ = ::Versionomy.version_of(mod_)
+          assert_not_nil(value_)
+          assert_not_equal(zero_, value_)
+        end
+      end
+
+
+    end
+
+  end
+end

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-versionomy.git



More information about the Pkg-ruby-extras-commits mailing list