├── Version ├── .gitignore ├── .travis.yml ├── Gemfile ├── lib ├── versionomy │ ├── version.rb │ ├── conversion │ │ ├── base.rb │ │ └── parsing.rb │ ├── schema.rb │ ├── errors.rb │ ├── format │ │ └── base.rb │ ├── conversion.rb │ ├── interface.rb │ ├── format.rb │ ├── format_definitions │ │ ├── semver.rb │ │ └── rubygems.rb │ └── schema │ │ └── wrapper.rb └── versionomy.rb ├── test ├── tc_custom_format.rb ├── tc_version_of.rb ├── tc_standard_misc.rb ├── tc_standard_change.rb ├── tc_standard_reset.rb ├── tc_readme_examples.rb ├── tc_standard_comparison.rb ├── tc_standard_bump.rb ├── tc_standard_basic.rb ├── tc_semver_basic.rb ├── tc_semver_conversions.rb ├── tc_rubygems_conversions.rb ├── tc_rubygems_basic.rb └── tc_standard_parse.rb ├── versionomy.gemspec ├── History.rdoc ├── README.rdoc ├── Rakefile └── Versionomy.rdoc /Version: -------------------------------------------------------------------------------- 1 | 0.5.0 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /doc/ 2 | /pkg/ 3 | .rbx/ 4 | .DS_Store 5 | *.rbc 6 | /Gemfile.lock 7 | .ruby-version 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.9.3 4 | - 2.0.0 5 | - 2.1 6 | - 2.2 7 | - 2.3.0 8 | - jruby 9 | - rbx 10 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy Gemfile 4 | # 5 | # This file indicates gems needed for testing. 6 | # 7 | # ----------------------------------------------------------------------------- 8 | # Copyright 2012 Daniel Azuma 9 | # 10 | # All rights reserved. 11 | # 12 | # Redistribution and use in source and binary forms, with or without 13 | # modification, are permitted provided that the following conditions are met: 14 | # 15 | # * Redistributions of source code must retain the above copyright notice, 16 | # this list of conditions and the following disclaimer. 17 | # * Redistributions in binary form must reproduce the above copyright notice, 18 | # this list of conditions and the following disclaimer in the documentation 19 | # and/or other materials provided with the distribution. 20 | # * Neither the name of the copyright holder, nor the names of any other 21 | # contributors to this software, may be used to endorse or promote products 22 | # derived from this software without specific prior written permission. 23 | # 24 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 28 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 | # POSSIBILITY OF SUCH DAMAGE. 35 | # ----------------------------------------------------------------------------- 36 | ; 37 | 38 | 39 | source "http://rubygems.org/" 40 | 41 | gem('blockenspiel', '>= 0.5.0') 42 | 43 | group(:test) do 44 | gem('minitest', '>= 5.8') 45 | gem('rake', '>= 10.0') 46 | gem('rdoc', '>= 4.2') 47 | end 48 | -------------------------------------------------------------------------------- /lib/versionomy/version.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy version 4 | # 5 | # ----------------------------------------------------------------------------- 6 | # Copyright 2008 Daniel Azuma 7 | # 8 | # All rights reserved. 9 | # 10 | # Redistribution and use in source and binary forms, with or without 11 | # modification, are permitted provided that the following conditions are met: 12 | # 13 | # * Redistributions of source code must retain the above copyright notice, 14 | # this list of conditions and the following disclaimer. 15 | # * Redistributions in binary form must reproduce the above copyright notice, 16 | # this list of conditions and the following disclaimer in the documentation 17 | # and/or other materials provided with the distribution. 18 | # * Neither the name of the copyright holder, nor the names of any other 19 | # contributors to this software, may be used to endorse or promote products 20 | # derived from this software without specific prior written permission. 21 | # 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 26 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | # POSSIBILITY OF SUCH DAMAGE. 33 | # ----------------------------------------------------------------------------- 34 | ; 35 | 36 | 37 | module Versionomy 38 | 39 | # Current gem version, as a frozen string. 40 | VERSION_STRING = ::File.read(::File.dirname(__FILE__)+'/../../Version').strip.freeze 41 | 42 | # Current gem version, as a Versionomy::Value. 43 | VERSION = ::Versionomy.parse(VERSION_STRING, :standard) 44 | 45 | end 46 | -------------------------------------------------------------------------------- /lib/versionomy.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy entry point 4 | # 5 | # ----------------------------------------------------------------------------- 6 | # Copyright 2008 Daniel Azuma 7 | # 8 | # All rights reserved. 9 | # 10 | # Redistribution and use in source and binary forms, with or without 11 | # modification, are permitted provided that the following conditions are met: 12 | # 13 | # * Redistributions of source code must retain the above copyright notice, 14 | # this list of conditions and the following disclaimer. 15 | # * Redistributions in binary form must reproduce the above copyright notice, 16 | # this list of conditions and the following disclaimer in the documentation 17 | # and/or other materials provided with the distribution. 18 | # * Neither the name of the copyright holder, nor the names of any other 19 | # contributors to this software, may be used to endorse or promote products 20 | # derived from this software without specific prior written permission. 21 | # 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 26 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | # POSSIBILITY OF SUCH DAMAGE. 33 | # ----------------------------------------------------------------------------- 34 | ; 35 | 36 | 37 | require 'blockenspiel' 38 | 39 | require 'versionomy/errors' 40 | require 'versionomy/schema' 41 | require 'versionomy/schema/field' 42 | require 'versionomy/schema/wrapper' 43 | require 'versionomy/format' 44 | require 'versionomy/format/base' 45 | require 'versionomy/format/delimiter' 46 | require 'versionomy/value' 47 | require 'versionomy/conversion' 48 | require 'versionomy/conversion/base' 49 | require 'versionomy/conversion/parsing' 50 | require 'versionomy/interface' 51 | require 'versionomy/version' 52 | -------------------------------------------------------------------------------- /test/tc_custom_format.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy parsing tests on standard schema 4 | # 5 | # This file contains tests for parsing on the standard schema 6 | # 7 | # ----------------------------------------------------------------------------- 8 | # Copyright 2008 Daniel Azuma 9 | # 10 | # All rights reserved. 11 | # 12 | # Redistribution and use in source and binary forms, with or without 13 | # modification, are permitted provided that the following conditions are met: 14 | # 15 | # * Redistributions of source code must retain the above copyright notice, 16 | # this list of conditions and the following disclaimer. 17 | # * Redistributions in binary form must reproduce the above copyright notice, 18 | # this list of conditions and the following disclaimer in the documentation 19 | # and/or other materials provided with the distribution. 20 | # * Neither the name of the copyright holder, nor the names of any other 21 | # contributors to this software, may be used to endorse or promote products 22 | # derived from this software without specific prior written permission. 23 | # 24 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 28 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 | # POSSIBILITY OF SUCH DAMAGE. 35 | # ----------------------------------------------------------------------------- 36 | 37 | 38 | require 'minitest/autorun' 39 | require 'versionomy' 40 | 41 | 42 | module Versionomy 43 | module Tests # :nodoc: 44 | 45 | class TestCustomFormat < ::Minitest::Test # :nodoc: 46 | 47 | 48 | # Test parsing with custom format for patchlevel 49 | 50 | def test_parsing_custom_patchlevel_format 51 | format_ = ::Versionomy.default_format.modified_copy do 52 | field(:patchlevel, :requires_previous_field => false) do 53 | recognize_number(:delimiter_regexp => '\s?sp', :default_delimiter => ' SP') 54 | end 55 | end 56 | value1_ = ::Versionomy.parse('2008 SP2', format_) 57 | assert_equal(2, value1_.patchlevel) 58 | value2_ = value1_.format.parse('2008 sp3') 59 | assert_equal(3, value2_.patchlevel) 60 | end 61 | 62 | 63 | end 64 | 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /versionomy.gemspec: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy gemspec 4 | # 5 | # This file contains the gemspec for Versionomy. 6 | # 7 | # ----------------------------------------------------------------------------- 8 | # Copyright 2011 Daniel Azuma 9 | # 10 | # All rights reserved. 11 | # 12 | # Redistribution and use in source and binary forms, with or without 13 | # modification, are permitted provided that the following conditions are met: 14 | # 15 | # * Redistributions of source code must retain the above copyright notice, 16 | # this list of conditions and the following disclaimer. 17 | # * Redistributions in binary form must reproduce the above copyright notice, 18 | # this list of conditions and the following disclaimer in the documentation 19 | # and/or other materials provided with the distribution. 20 | # * Neither the name of the copyright holder, nor the names of any other 21 | # contributors to this software, may be used to endorse or promote products 22 | # derived from this software without specific prior written permission. 23 | # 24 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 28 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 | # POSSIBILITY OF SUCH DAMAGE. 35 | # ----------------------------------------------------------------------------- 36 | ; 37 | 38 | 39 | ::Gem::Specification.new do |s_| 40 | s_.name = 'versionomy' 41 | s_.summary = 'Versionomy is a generalized version number library.' 42 | s_.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.' 43 | s_.version = "#{::File.read('Version').strip}.nonrelease" 44 | s_.licenses = ['BSD-3-Clause'] 45 | s_.author = 'Daniel Azuma' 46 | s_.email = 'dazuma@gmail.com' 47 | s_.homepage = 'http://dazuma.github.com/versionomy' 48 | s_.rubyforge_project = 'virtuoso' 49 | s_.required_ruby_version = '>= 1.9.3' 50 | s_.files = ::Dir.glob("lib/**/*.rb") + 51 | ::Dir.glob("test/**/*.rb") + 52 | ::Dir.glob("*.rdoc") + 53 | ['Version'] 54 | s_.extra_rdoc_files = ::Dir.glob("*.rdoc") 55 | s_.test_files = ::Dir.glob("test/**/tc_*.rb") 56 | s_.platform = ::Gem::Platform::RUBY 57 | s_.add_dependency('blockenspiel', '~> 0.5') 58 | end 59 | -------------------------------------------------------------------------------- /test/tc_version_of.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy basic tests on standard schema 4 | # 5 | # This file contains tests for the basic use cases on the standard schema 6 | # 7 | # ----------------------------------------------------------------------------- 8 | # Copyright 2008 Daniel Azuma 9 | # 10 | # All rights reserved. 11 | # 12 | # Redistribution and use in source and binary forms, with or without 13 | # modification, are permitted provided that the following conditions are met: 14 | # 15 | # * Redistributions of source code must retain the above copyright notice, 16 | # this list of conditions and the following disclaimer. 17 | # * Redistributions in binary form must reproduce the above copyright notice, 18 | # this list of conditions and the following disclaimer in the documentation 19 | # and/or other materials provided with the distribution. 20 | # * Neither the name of the copyright holder, nor the names of any other 21 | # contributors to this software, may be used to endorse or promote products 22 | # derived from this software without specific prior written permission. 23 | # 24 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 28 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 | # POSSIBILITY OF SUCH DAMAGE. 35 | # ----------------------------------------------------------------------------- 36 | 37 | 38 | require 'minitest/autorun' 39 | require 'versionomy' 40 | 41 | 42 | module Versionomy 43 | module Tests # :nodoc: 44 | 45 | class TestVersionOf < ::Minitest::Test # :nodoc: 46 | 47 | 48 | # Gems to test if we can 49 | GEM_LIST = { 50 | 'activerecord' => {:require => 'active_record', :module_name => 'ActiveRecord'}, 51 | 'blockenspiel' => {:module_name => 'Blockenspiel'}, 52 | 'bundler' => {:module_name => 'Bundler'}, 53 | 'erubis' => {:module_name => 'Erubis'}, 54 | } 55 | 56 | 57 | # Engine that tests each gem if it's installed 58 | zero_ = ::Versionomy.create(:major => 0) 59 | GEM_LIST.each do |name_, data_| 60 | unless data_.kind_of?(::Hash) 61 | data_ = {:module_name => data_} 62 | end 63 | begin 64 | gem name_ 65 | require data_[:require] || name_ 66 | rescue ::LoadError 67 | next 68 | end 69 | define_method("test_gem_#{name_}") do 70 | mod_ = eval(data_[:module_name]) 71 | value_ = ::Versionomy.version_of(mod_) 72 | refute_nil(value_) 73 | refute_equal(zero_, value_) 74 | end 75 | end 76 | 77 | 78 | end 79 | 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/versionomy/conversion/base.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy conversion base class 4 | # 5 | # ----------------------------------------------------------------------------- 6 | # Copyright 2008 Daniel Azuma 7 | # 8 | # All rights reserved. 9 | # 10 | # Redistribution and use in source and binary forms, with or without 11 | # modification, are permitted provided that the following conditions are met: 12 | # 13 | # * Redistributions of source code must retain the above copyright notice, 14 | # this list of conditions and the following disclaimer. 15 | # * Redistributions in binary form must reproduce the above copyright notice, 16 | # this list of conditions and the following disclaimer in the documentation 17 | # and/or other materials provided with the distribution. 18 | # * Neither the name of the copyright holder, nor the names of any other 19 | # contributors to this software, may be used to endorse or promote products 20 | # derived from this software without specific prior written permission. 21 | # 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 26 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | # POSSIBILITY OF SUCH DAMAGE. 33 | # ----------------------------------------------------------------------------- 34 | ; 35 | 36 | 37 | module Versionomy 38 | 39 | module Conversion 40 | 41 | 42 | # The base conversion class. 43 | # 44 | # This base class defines the API for a conversion. All conversions must 45 | # define the method convert_value documented here. Conversions 46 | # need not actually extend this base class, as long as they duck-type 47 | # this method. However, this base class does provide a few convenience 48 | # methods such as a sane implementation of inspect. 49 | 50 | class Base 51 | 52 | 53 | # Create a conversion using a simple DSL. 54 | # You can pass a block to the initializer that takes the same 55 | # parameters as convert_value, and the conversion will use that block 56 | # to perform the conversion. 57 | 58 | def initialize(&block_) 59 | @_converter = block_ 60 | end 61 | 62 | 63 | # Convert the given value to the given format and return the converted 64 | # value. 65 | # 66 | # The convert_params may be interpreted however the particular 67 | # conversion wishes. 68 | # 69 | # Raises Versionomy::Errors::ConversionError if the conversion failed. 70 | 71 | def convert_value(value_, format_, convert_params_=nil) 72 | if @_converter 73 | @_converter.call(value_, format_, convert_params_) 74 | else 75 | raise Errors::ConversionError, "Conversion not implemented" 76 | end 77 | end 78 | 79 | 80 | # Inspect this conversion. 81 | 82 | def inspect 83 | "#<#{self.class}:0x#{object_id.to_s(16)}>" 84 | end 85 | 86 | 87 | # The default to_s implementation just calls inspect. 88 | 89 | def to_s 90 | inspect 91 | end 92 | 93 | 94 | end 95 | 96 | 97 | end 98 | 99 | end 100 | -------------------------------------------------------------------------------- /test/tc_standard_misc.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy basic tests on standard schema 4 | # 5 | # This file contains tests for the basic use cases on the standard schema 6 | # 7 | # ----------------------------------------------------------------------------- 8 | # Copyright 2008 Daniel Azuma 9 | # 10 | # All rights reserved. 11 | # 12 | # Redistribution and use in source and binary forms, with or without 13 | # modification, are permitted provided that the following conditions are met: 14 | # 15 | # * Redistributions of source code must retain the above copyright notice, 16 | # this list of conditions and the following disclaimer. 17 | # * Redistributions in binary form must reproduce the above copyright notice, 18 | # this list of conditions and the following disclaimer in the documentation 19 | # and/or other materials provided with the distribution. 20 | # * Neither the name of the copyright holder, nor the names of any other 21 | # contributors to this software, may be used to endorse or promote products 22 | # derived from this software without specific prior written permission. 23 | # 24 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 28 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 | # POSSIBILITY OF SUCH DAMAGE. 35 | # ----------------------------------------------------------------------------- 36 | 37 | 38 | require 'minitest/autorun' 39 | require 'versionomy' 40 | require 'yaml' 41 | 42 | module Versionomy 43 | module Tests # :nodoc: 44 | 45 | class TestStandardMisc < ::Minitest::Test # :nodoc: 46 | 47 | 48 | # Test "prerelase?" custom method 49 | 50 | def test_method_prereleasep 51 | value_ = ::Versionomy.create(:major => 2, :tiny => 1, :release_type => :beta, :beta_version => 3) 52 | assert_equal(true, value_.prerelease?) 53 | value_ = ::Versionomy.create(:major => 2, :tiny => 1, :release_type => :final, :patchlevel => 1) 54 | assert_equal(false, value_.prerelease?) 55 | value_ = ::Versionomy.create(:major => 2, :tiny => 1) 56 | assert_equal(false, value_.prerelease?) 57 | end 58 | 59 | 60 | # Test "relase" custom method 61 | 62 | def test_method_release 63 | value_ = ::Versionomy.create(:major => 1, :minor => 9, :tiny => 2, :release_type => :alpha, :alpha_version => 4) 64 | value2_ = value_.release 65 | assert_equal([1, 9, 2, 0, :final, 0, 0], value2_.values_array) 66 | value_ = ::Versionomy.create(:major => 1, :minor => 9, :tiny => 2) 67 | value2_ = value_.release 68 | assert_equal(value_, value2_) 69 | end 70 | 71 | 72 | # Test marshalling 73 | 74 | def test_marshal 75 | value_ = ::Versionomy.create(:major => 1, :minor => 9, :tiny => 2, :release_type => :alpha, :alpha_version => 4) 76 | str_ = ::Marshal.dump(value_) 77 | value2_ = ::Marshal.load(str_) 78 | assert_equal(value_, value2_) 79 | end 80 | 81 | 82 | # Test YAML 83 | 84 | def test_yaml 85 | value_ = ::Versionomy.create(:major => 1, :minor => 9, :tiny => 2, :release_type => :alpha, :alpha_version => 4) 86 | str_ = ::YAML.dump(value_) 87 | value2_ = ::YAML.load(str_) 88 | assert_equal(value_, value2_) 89 | end 90 | 91 | 92 | end 93 | 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /test/tc_standard_change.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy change tests on standard schema 4 | # 5 | # This file contains tests for the change function on the standard schema 6 | # 7 | # ----------------------------------------------------------------------------- 8 | # Copyright 2008 Daniel Azuma 9 | # 10 | # All rights reserved. 11 | # 12 | # Redistribution and use in source and binary forms, with or without 13 | # modification, are permitted provided that the following conditions are met: 14 | # 15 | # * Redistributions of source code must retain the above copyright notice, 16 | # this list of conditions and the following disclaimer. 17 | # * Redistributions in binary form must reproduce the above copyright notice, 18 | # this list of conditions and the following disclaimer in the documentation 19 | # and/or other materials provided with the distribution. 20 | # * Neither the name of the copyright holder, nor the names of any other 21 | # contributors to this software, may be used to endorse or promote products 22 | # derived from this software without specific prior written permission. 23 | # 24 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 28 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 | # POSSIBILITY OF SUCH DAMAGE. 35 | # ----------------------------------------------------------------------------- 36 | 37 | 38 | require 'minitest/autorun' 39 | require 'versionomy' 40 | 41 | 42 | module Versionomy 43 | module Tests # :nodoc: 44 | 45 | class TestStandardChange < ::Minitest::Test # :nodoc: 46 | 47 | 48 | # Test with a changed tiny 49 | 50 | def test_change_tiny 51 | value_ = ::Versionomy.create(:major => 2, :tiny => 1, :tiny2 => 3, :release_type => :release_candidate, :release_candidate_version => 2) 52 | value_ = value_.change(:tiny => 4) 53 | assert_equal(2, value_.major) 54 | assert_equal(0, value_.minor) 55 | assert_equal(4, value_.tiny) 56 | assert_equal(3, value_.tiny2) 57 | assert_equal(:release_candidate, value_.release_type) 58 | assert_equal(2, value_.release_candidate_version) 59 | assert_equal(0, value_.release_candidate_minor) 60 | end 61 | 62 | 63 | # Test with several changed fields 64 | 65 | def test_change_several 66 | value_ = ::Versionomy.create(:major => 2, :tiny => 1, :tiny2 => 3, :release_type => :release_candidate, :release_candidate_version => 2) 67 | value_ = value_.change(:tiny => 4, :release_candidate_version => 5) 68 | assert_equal(2, value_.major) 69 | assert_equal(0, value_.minor) 70 | assert_equal(4, value_.tiny) 71 | assert_equal(3, value_.tiny2) 72 | assert_equal(:release_candidate, value_.release_type) 73 | assert_equal(5, value_.release_candidate_version) 74 | assert_equal(0, value_.release_candidate_minor) 75 | end 76 | 77 | 78 | # Test with a changed release type 79 | 80 | def test_change_release_type 81 | value_ = ::Versionomy.create(:major => 2, :tiny => 1, :tiny2 => 3, :release_type => :release_candidate, :release_candidate_version => 2) 82 | value_ = value_.change(:release_type => :beta) 83 | assert_equal(2, value_.major) 84 | assert_equal(0, value_.minor) 85 | assert_equal(1, value_.tiny) 86 | assert_equal(3, value_.tiny2) 87 | assert_equal(:beta, value_.release_type) 88 | assert_equal(1, value_.beta_version) 89 | assert_equal(0, value_.beta_minor) 90 | assert_equal(false, value_.has_field?(:release_candidate_version)) 91 | assert_equal(false, value_.has_field?(:release_candidate_minor)) 92 | end 93 | 94 | 95 | end 96 | 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /test/tc_standard_reset.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy bump tests on standard schema 4 | # 5 | # This file contains tests for the bump function on the standard schema 6 | # 7 | # ----------------------------------------------------------------------------- 8 | # Copyright 2008 Daniel Azuma 9 | # 10 | # All rights reserved. 11 | # 12 | # Redistribution and use in source and binary forms, with or without 13 | # modification, are permitted provided that the following conditions are met: 14 | # 15 | # * Redistributions of source code must retain the above copyright notice, 16 | # this list of conditions and the following disclaimer. 17 | # * Redistributions in binary form must reproduce the above copyright notice, 18 | # this list of conditions and the following disclaimer in the documentation 19 | # and/or other materials provided with the distribution. 20 | # * Neither the name of the copyright holder, nor the names of any other 21 | # contributors to this software, may be used to endorse or promote products 22 | # derived from this software without specific prior written permission. 23 | # 24 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 28 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 | # POSSIBILITY OF SUCH DAMAGE. 35 | # ----------------------------------------------------------------------------- 36 | 37 | 38 | require 'minitest/autorun' 39 | require 'versionomy' 40 | 41 | 42 | module Versionomy 43 | module Tests # :nodoc: 44 | 45 | class TestStandardReset < ::Minitest::Test # :nodoc: 46 | 47 | 48 | # Test resetting a minor patchlevel. 49 | 50 | def test_reset_patchlevel_minor 51 | value_ = ::Versionomy.create(:major => 2, :tiny => 1, :patchlevel => 3, :patchlevel_minor => 1) 52 | value_ = value_.reset(:patchlevel_minor) 53 | assert_equal([2,0,1,0,:final,3,0], value_.values_array) 54 | end 55 | 56 | 57 | # Test resetting a major patchlevel. 58 | 59 | def test_reset_patchlevel 60 | value_ = ::Versionomy.create(:major => 2, :tiny => 1, :patchlevel => 3, :patchlevel_minor => 1) 61 | value_ = value_.reset(:patchlevel) 62 | assert_equal([2,0,1,0,:final,0,0], value_.values_array) 63 | end 64 | 65 | 66 | # Test resetting a beta release type. 67 | 68 | def test_reset_beta_release_type 69 | value_ = ::Versionomy.create(:major => 2, :tiny => 1, :release_type => :beta, :beta_version => 2) 70 | value_ = value_.reset(:release_type) 71 | assert_equal([2,0,1,0,:final,0,0], value_.values_array) 72 | end 73 | 74 | 75 | # Test resetting a final release type. 76 | 77 | def test_reset_final_release_type 78 | value_ = ::Versionomy.create(:major => 2, :tiny => 1, :patchlevel => 2) 79 | value_ = value_.reset(:release_type) 80 | assert_equal([2,0,1,0,:final,0,0], value_.values_array) 81 | end 82 | 83 | 84 | # Test resetting tiny. 85 | 86 | def test_reset_tiny 87 | value_ = ::Versionomy.create(:major => 2, :tiny => 1, :tiny2 => 3, :release_type => :release_candidate, :release_candidate_version => 2) 88 | value_ = value_.reset(:tiny) 89 | assert_equal([2,0,0,0,:final,0,0], value_.values_array) 90 | end 91 | 92 | 93 | # Test resetting major. 94 | 95 | def test_reset_major 96 | value_ = ::Versionomy.create(:major => 2, :tiny => 1, :tiny2 => 3, :release_type => :release_candidate, :release_candidate_version => 2) 97 | value_ = value_.reset(:major) 98 | assert_equal([1,0,0,0,:final,0,0], value_.values_array) 99 | end 100 | 101 | 102 | end 103 | 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /lib/versionomy/schema.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy schema namespace 4 | # 5 | # ----------------------------------------------------------------------------- 6 | # Copyright 2008 Daniel Azuma 7 | # 8 | # All rights reserved. 9 | # 10 | # Redistribution and use in source and binary forms, with or without 11 | # modification, are permitted provided that the following conditions are met: 12 | # 13 | # * Redistributions of source code must retain the above copyright notice, 14 | # this list of conditions and the following disclaimer. 15 | # * Redistributions in binary form must reproduce the above copyright notice, 16 | # this list of conditions and the following disclaimer in the documentation 17 | # and/or other materials provided with the distribution. 18 | # * Neither the name of the copyright holder, nor the names of any other 19 | # contributors to this software, may be used to endorse or promote products 20 | # derived from this software without specific prior written permission. 21 | # 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 26 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | # POSSIBILITY OF SUCH DAMAGE. 33 | # ----------------------------------------------------------------------------- 34 | ; 35 | 36 | 37 | module Versionomy 38 | 39 | 40 | # === Version number schema. 41 | # 42 | # A schema defines the structure and semantics of a version number. 43 | # The schema controls what fields are present in the version, how 44 | # version numbers are compared, what the default values are, and how 45 | # values can change. Version numbers with the same schema can be 46 | # compared with one another, and version numbers can be converted 47 | # trivially to formats that share the same schema, without requiring a 48 | # Conversion implementation. 49 | # 50 | # At its simplest, a version number is defined as a sequence of fields, 51 | # each with a name and data type. These fields may be integer-valued, 52 | # string-valued, or symbolic, though most will probably be integers. 53 | # Symbolic fields are enumerated types that are useful, for example, if 54 | # you want a field to specify the type of prerelease (e.g. "alpha", 55 | # "beta", or "release candidate"). 56 | # 57 | # As a simple conceptual example, you could construct a schema for 58 | # version numbers of the form "major.minor.tiny" like this. (This is a 59 | # conceptual diagram, not actual syntax.) 60 | # 61 | # ("major": integer), ("minor": integer), ("tiny": integer) 62 | # 63 | # More generally, fields are actually organized into a DAG (directed 64 | # acyclic graph) in which the "most significant" field is the root, the 65 | # next most significant is a child of that root, and so forth down the 66 | # line. The simple schema above, then, is actually represented as a 67 | # linked list (a graph with one path), like this: 68 | # 69 | # ("major": integer) -> 70 | # ("minor": integer) -> 71 | # ("tiny": integer) -> 72 | # nil 73 | # 74 | # It is, however, possible for the form of a field to depend on the value 75 | # of the previous field. For example, suppose we wanted a schema in which 76 | # if the value of the "minor" field is 0, then the "tiny" field doesn't 77 | # exist. e.g. 78 | # 79 | # ("major": integer) -> 80 | # ("minor": integer) -> 81 | # [value == 0] : nil 82 | # [otherwise] : ("tiny": integer) -> 83 | # nil 84 | # 85 | # The Versionomy::Schema::Field class represents a field in this graph. 86 | # The Versionomy::Schema::Wrapper class represents a full schema object. 87 | # 88 | # Generally, you should create schemas using Versionomy::Schema#create. 89 | # That method provides a DSL that lets you quickly create the fields. 90 | 91 | module Schema 92 | end 93 | 94 | 95 | end 96 | -------------------------------------------------------------------------------- /test/tc_readme_examples.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy tests of the README examples 4 | # 5 | # This file contains tests to ensure the README is valid 6 | # 7 | # ----------------------------------------------------------------------------- 8 | # Copyright 2008 Daniel Azuma 9 | # 10 | # All rights reserved. 11 | # 12 | # Redistribution and use in source and binary forms, with or without 13 | # modification, are permitted provided that the following conditions are met: 14 | # 15 | # * Redistributions of source code must retain the above copyright notice, 16 | # this list of conditions and the following disclaimer. 17 | # * Redistributions in binary form must reproduce the above copyright notice, 18 | # this list of conditions and the following disclaimer in the documentation 19 | # and/or other materials provided with the distribution. 20 | # * Neither the name of the copyright holder, nor the names of any other 21 | # contributors to this software, may be used to endorse or promote products 22 | # derived from this software without specific prior written permission. 23 | # 24 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 28 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 | # POSSIBILITY OF SUCH DAMAGE. 35 | # ----------------------------------------------------------------------------- 36 | 37 | 38 | require 'minitest/autorun' 39 | require 'versionomy' 40 | 41 | 42 | module Versionomy 43 | module Tests # :nodoc: 44 | 45 | class TestReadmeExamples < ::Minitest::Test # :nodoc: 46 | 47 | 48 | # Test the README file. 49 | # This actually reads the README file and does some eval magic 50 | # to run it and ensure it works the way it claims. 51 | 52 | def test_readme_file 53 | binding_ = _get_binding 54 | ::File.open("#{::File.dirname(__FILE__)}/../README.rdoc") do |io_| 55 | running_ = false 56 | buffer_ = '' 57 | buffer_start_line_ = nil 58 | io_.each_line do |line_| 59 | 60 | # Run code in the "Some examples" section. 61 | running_ = false if line_ =~ /^===/ 62 | running_ = true if line_ =~ /^=== Some examples/ 63 | next unless running_ && line_[0,1] == ' ' 64 | # Skip the require line 65 | next if line_ =~ /^\s+require/ 66 | 67 | # If there isn't an expects clause, then collect the code into 68 | # a buffer to run all at once, because it might be code that 69 | # gets spread over multiple lines. 70 | delim_index_ = line_.index(' # ') 71 | if !delim_index_ || line_[0, delim_index_].strip.length == 0 72 | buffer_start_line_ ||= io_.lineno 73 | buffer_ << line_ 74 | next 75 | end 76 | 77 | # At this point, we have an expects clause. First run any buffer 78 | # accumulated up to now. 79 | if buffer_.length > 0 80 | ::Kernel.eval(buffer_, binding_, 'README.rdoc', buffer_start_line_) 81 | buffer_ = '' 82 | buffer_start_line_ = nil 83 | end 84 | 85 | # Parse the line into an expression and an expectation 86 | expr_ = line_[0,delim_index_] 87 | expect_ = line_[delim_index_+3..-1] 88 | 89 | if expect_ =~ /^=> (.*)$/ 90 | # Expect a value 91 | expect_value_ = ::Kernel.eval($1, binding_, 'README.rdoc', io_.lineno) 92 | actual_value_ = ::Kernel.eval(expr_, binding_, 'README.rdoc', io_.lineno) 93 | assert_equal(expect_value_, actual_value_, 94 | "Values did not match on line #{io_.lineno} of README.rdoc") 95 | 96 | elsif expect_ =~ /^raises (.*)$/ 97 | # Expect an exception to be raised 98 | expect_error_ = ::Kernel.eval($1, binding_, 'README.rdoc', io_.lineno) 99 | assert_raises(expect_error_) do 100 | ::Kernel.eval(expr_, binding_, 'README.rdoc', io_.lineno) 101 | end 102 | 103 | else 104 | raise "Unknown expect syntax: #{expect_.inspect}" 105 | end 106 | 107 | end 108 | end 109 | end 110 | 111 | 112 | def _get_binding 113 | binding 114 | end 115 | 116 | 117 | end 118 | 119 | end 120 | end 121 | -------------------------------------------------------------------------------- /lib/versionomy/errors.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy exceptions 4 | # 5 | # ----------------------------------------------------------------------------- 6 | # Copyright 2008 Daniel Azuma 7 | # 8 | # All rights reserved. 9 | # 10 | # Redistribution and use in source and binary forms, with or without 11 | # modification, are permitted provided that the following conditions are met: 12 | # 13 | # * Redistributions of source code must retain the above copyright notice, 14 | # this list of conditions and the following disclaimer. 15 | # * Redistributions in binary form must reproduce the above copyright notice, 16 | # this list of conditions and the following disclaimer in the documentation 17 | # and/or other materials provided with the distribution. 18 | # * Neither the name of the copyright holder, nor the names of any other 19 | # contributors to this software, may be used to endorse or promote products 20 | # derived from this software without specific prior written permission. 21 | # 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 26 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | # POSSIBILITY OF SUCH DAMAGE. 33 | # ----------------------------------------------------------------------------- 34 | ; 35 | 36 | 37 | module Versionomy 38 | 39 | 40 | # This is a namespace for errors that can be thrown by Versionomy. 41 | 42 | module Errors 43 | 44 | 45 | # Base class for all Versionomy exceptions 46 | 47 | class VersionomyError < ::RuntimeError 48 | end 49 | 50 | 51 | # This exception is raised if parsing failed. 52 | 53 | class ParseError < VersionomyError 54 | end 55 | 56 | 57 | # This exception is raised if unparsing failed. 58 | 59 | class UnparseError < VersionomyError 60 | end 61 | 62 | 63 | # This exception is raised if you try to set a value that is not 64 | # allowed by the schema. 65 | 66 | class IllegalValueError < VersionomyError 67 | end 68 | 69 | 70 | # This exception is raised if you try to perform a comparison 71 | # between incompatible schemas. 72 | 73 | class SchemaMismatchError < VersionomyError 74 | end 75 | 76 | 77 | # Base class for all Versionomy schema creation exceptions 78 | 79 | class SchemaCreationError < VersionomyError 80 | end 81 | 82 | 83 | # This exception is raised during schema creation if you try to add 84 | # the same symbol twice to the same symbolic field. 85 | 86 | class SymbolRedefinedError < SchemaCreationError 87 | end 88 | 89 | 90 | # This exception is raised during schema creation if you try to 91 | # create two fields covering overlapping ranges. 92 | 93 | class RangeOverlapError < SchemaCreationError 94 | end 95 | 96 | 97 | # This exception is raised during schema creation if the range 98 | # specification cannot be interpreted. 99 | 100 | class RangeSpecificationError < SchemaCreationError 101 | end 102 | 103 | 104 | # This exception is raised during schema creation if you try to 105 | # add a symbol to a non-symbolic schema. 106 | 107 | class TypeMismatchError < SchemaCreationError 108 | end 109 | 110 | 111 | # This exception is raised during schema creation if you try to 112 | # create a circular graph. 113 | 114 | class CircularDescendantError < SchemaCreationError 115 | end 116 | 117 | 118 | # Base class for all Versionomy format creation exceptions. 119 | 120 | class FormatCreationError < VersionomyError 121 | end 122 | 123 | 124 | # This exception is raised if you try to register a format 125 | # with a name that has already been used. 126 | 127 | class FormatRedefinedError < VersionomyError 128 | end 129 | 130 | 131 | # Raised by the Format registry if you try to retrieve a format with 132 | # an unrecognized name in strict mode. 133 | 134 | class UnknownFormatError < VersionomyError 135 | end 136 | 137 | 138 | # Raised when a conversion fails. 139 | 140 | class ConversionError < VersionomyError 141 | end 142 | 143 | 144 | # Raised when a conversion fails because no conversion implementation 145 | # was found. 146 | 147 | class UnknownConversionError < ConversionError 148 | end 149 | 150 | 151 | # Raised when you try to register a conversion when one already 152 | # exists for its schemas. 153 | 154 | class ConversionRedefinedError < VersionomyError 155 | end 156 | 157 | 158 | end 159 | 160 | end 161 | -------------------------------------------------------------------------------- /lib/versionomy/format/base.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy format base class 4 | # 5 | # ----------------------------------------------------------------------------- 6 | # Copyright 2008 Daniel Azuma 7 | # 8 | # All rights reserved. 9 | # 10 | # Redistribution and use in source and binary forms, with or without 11 | # modification, are permitted provided that the following conditions are met: 12 | # 13 | # * Redistributions of source code must retain the above copyright notice, 14 | # this list of conditions and the following disclaimer. 15 | # * Redistributions in binary form must reproduce the above copyright notice, 16 | # this list of conditions and the following disclaimer in the documentation 17 | # and/or other materials provided with the distribution. 18 | # * Neither the name of the copyright holder, nor the names of any other 19 | # contributors to this software, may be used to endorse or promote products 20 | # derived from this software without specific prior written permission. 21 | # 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 26 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | # POSSIBILITY OF SUCH DAMAGE. 33 | # ----------------------------------------------------------------------------- 34 | ; 35 | 36 | 37 | module Versionomy 38 | 39 | module Format 40 | 41 | 42 | # The base format. 43 | # 44 | # This format doesn't actually do anything useful. It causes all strings 45 | # to parse to the schema's default value, and unparses all values to the 46 | # empty string. Instead, the purpose here is to define the API for a 47 | # format. 48 | # 49 | # All formats must define the methods +schema+, +parse+, and +unparse+. 50 | # It is also recommended that formats define the === method, 51 | # though this is not strictly required. Finally, formats may optionally 52 | # implement uparse_for_serialize. 53 | # 54 | # Formats need not extend this base class, as long as they duck-type 55 | # these methods. 56 | 57 | class Base 58 | 59 | 60 | # Create an instance of this base format, with the given schema. 61 | 62 | def initialize(schema_) 63 | @_schema = schema_ 64 | end 65 | 66 | 67 | def inspect # :nodoc: 68 | "#<#{self.class}:0x#{object_id.to_s(16)} schema=#{@_schema.inspect}>" 69 | end 70 | 71 | def to_s # :nodoc: 72 | inspect 73 | end 74 | 75 | 76 | # Returns the schema understood by this format. 77 | 78 | def schema 79 | @_schema 80 | end 81 | 82 | 83 | # Parse the given string and return a value. 84 | # 85 | # The optional parameter hash can be used to pass parameters to the 86 | # parser to affect its behavior. The exact parameters supported are 87 | # defined by the format. 88 | 89 | def parse(string_, params_=nil) 90 | Value.new([], self) 91 | end 92 | 93 | 94 | # Unparse the given value and return a string. 95 | # 96 | # The optional parameter hash can be used to pass parameters to the 97 | # unparser to affect its behavior. The exact parameters supported 98 | # are defined by the format. 99 | 100 | def unparse(value_, params_=nil) 101 | '' 102 | end 103 | 104 | 105 | # An optional method that does unparsing especially for serialization. 106 | # Implement this if normal unparsing is "lossy" and doesn't guarantee 107 | # reconstruction of the version number. This method should attempt to 108 | # unparse in such a way that the entire version value can be 109 | # reconstructed from the unparsed string. Serialization routines will 110 | # first attempt to call this method to unparse for serialization. If 111 | # this method is not present, the normal unparse method will be used. 112 | # 113 | # Return either the unparsed string, or an array consisting of the 114 | # unparsed string and a hash of parse params to pass to the parser 115 | # when the string is to be reconstructed. You may also either return 116 | # nil or raise Versionomy::Errors::UnparseError if the unparsing 117 | # cannot be done satisfactorily for serialization. In this case, 118 | # serialization will be done using the raw value data rather than an 119 | # unparsed string. 120 | # 121 | # This default implementation just turns around and calls unparse. 122 | # Thus it is equivalent to the method not being present at all. 123 | 124 | def unparse_for_serialization(value_) 125 | unparse(value_) 126 | end 127 | 128 | 129 | # Determine whether the given value uses this format. 130 | 131 | def ===(obj_) 132 | if obj_.kind_of?(Value) 133 | obj_.format == self 134 | else 135 | obj_ == self 136 | end 137 | end 138 | 139 | 140 | end 141 | 142 | 143 | end 144 | 145 | end 146 | -------------------------------------------------------------------------------- /test/tc_standard_comparison.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy comparison tests on standard schema 4 | # 5 | # This file contains tests for comparisons on the standard schema 6 | # 7 | # ----------------------------------------------------------------------------- 8 | # Copyright 2008 Daniel Azuma 9 | # 10 | # All rights reserved. 11 | # 12 | # Redistribution and use in source and binary forms, with or without 13 | # modification, are permitted provided that the following conditions are met: 14 | # 15 | # * Redistributions of source code must retain the above copyright notice, 16 | # this list of conditions and the following disclaimer. 17 | # * Redistributions in binary form must reproduce the above copyright notice, 18 | # this list of conditions and the following disclaimer in the documentation 19 | # and/or other materials provided with the distribution. 20 | # * Neither the name of the copyright holder, nor the names of any other 21 | # contributors to this software, may be used to endorse or promote products 22 | # derived from this software without specific prior written permission. 23 | # 24 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 28 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 | # POSSIBILITY OF SUCH DAMAGE. 35 | # ----------------------------------------------------------------------------- 36 | 37 | 38 | require 'minitest/autorun' 39 | require 'versionomy' 40 | 41 | 42 | module Versionomy 43 | module Tests # :nodoc: 44 | 45 | class TestStandardComparison < ::Minitest::Test # :nodoc: 46 | 47 | 48 | # Test comparisons with difference in major. 49 | 50 | def test_comparison_major 51 | value1_ = ::Versionomy.create(:major => 2, :tiny => 1, :tiny2 => 3, :release_type => :release_candidate, :release_candidate_version => 2) 52 | value2_ = ::Versionomy.create(:major => 3, :release_type => :alpha) 53 | assert(value2_ > value1_) 54 | end 55 | 56 | 57 | # Test comparisons with difference in minor. 58 | 59 | def test_comparison_minor 60 | value1_ = ::Versionomy.create(:major => 2, :tiny => 1, :tiny2 => 3, :release_type => :release_candidate, :release_candidate_version => 2) 61 | value2_ = ::Versionomy.create(:major => 2, :minor => 1, :release_type => :alpha) 62 | assert(value2_ > value1_) 63 | end 64 | 65 | 66 | # Test comparisons with difference in release type. 67 | 68 | def test_comparison_release_type 69 | value1_ = ::Versionomy.create(:major => 2, :release_type => :alpha, :alpha_version => 5) 70 | value2_ = ::Versionomy.create(:major => 2, :release_type => :release_candidate, :release_candidate_version => 2) 71 | assert(value2_ > value1_) 72 | end 73 | 74 | 75 | # Test equality for a simple case. 76 | 77 | def test_equality_simple 78 | value1_ = ::Versionomy.create(:major => 2, :minor => 0, :release_type => :alpha, :alpha_version => 5) 79 | value2_ = ::Versionomy.create(:major => 2, :release_type => :alpha, :alpha_version => 5) 80 | assert_equal(value2_, value1_) 81 | assert_equal(value2_.hash, value1_.hash) 82 | end 83 | 84 | 85 | # Test equality from parsed values. 86 | 87 | def test_equality_parsed 88 | value1_ = ::Versionomy.parse("1.8.7p72") 89 | value2_ = ::Versionomy.parse("1.8.7.0-72.0") 90 | assert_equal(value2_, value1_) 91 | assert_equal(value2_.hash, value1_.hash) 92 | end 93 | 94 | 95 | # Test non-equality from parsed values. 96 | 97 | def test_nonequality_parsed 98 | value1_ = ::Versionomy.parse("1.8.7b7") 99 | value2_ = ::Versionomy.parse("1.8.7a7") 100 | refute_equal(value2_, value1_) 101 | refute_equal(value2_.hash, value1_.hash) 102 | end 103 | 104 | 105 | # Test equality with string. 106 | 107 | def test_equality_string 108 | value1_ = ::Versionomy.parse("1.8.7p72") 109 | assert_operator(value1_, :==, "1.8.7p72") 110 | assert_operator(value1_, :==, "1.8.7.0-72.0") 111 | end 112 | 113 | 114 | # Test comparison with string. 115 | 116 | def test_comparison_string 117 | value1_ = ::Versionomy.parse("1.8.7p72") 118 | assert_operator(value1_, :<, "1.8.7p73") 119 | assert_operator(value1_, :<, "1.8.8pre1") 120 | assert_operator(value1_, :>, "1.8.7p71") 121 | assert_operator(value1_, :>, "1.8.7rc2") 122 | assert_operator(value1_, :>, "1.8.7.0") 123 | end 124 | 125 | 126 | # Test sorting. 127 | 128 | def test_sort 129 | value1_ = ::Versionomy.parse("1.8.7p73") 130 | value2_ = ::Versionomy.parse("1.8.7p72") 131 | value3_ = ::Versionomy.parse("1.8.8pre1") 132 | value4_ = ::Versionomy.parse("1.8.7.0") 133 | value5_ = ::Versionomy.parse("1.8.7rc2") 134 | assert_equal([value5_, value4_, value2_, value1_, value3_], 135 | [value1_, value2_, value3_, value4_, value5_].sort) 136 | end 137 | 138 | 139 | end 140 | 141 | end 142 | end 143 | -------------------------------------------------------------------------------- /History.rdoc: -------------------------------------------------------------------------------- 1 | === 0.5.0 / 2016-01-07 2 | 3 | * The gemspec no longer includes the timestamp in the version, so that bundler can pull from github. (Reported by corneverbruggen) 4 | * Updated the Rakefile, tests, and general infrastructure to play better with modern Rubies. 5 | * Dropped support for Ruby 1.8, because who still uses 1.8??? 6 | 7 | === 0.4.4 / 2012-06-27 8 | 9 | * Tried to be a little more robust against incomplete psych installations. 10 | * Travis CI integration. 11 | 12 | === 0.4.3 / 2012-03-27 13 | 14 | * Fixed a few warnings. 15 | 16 | === 0.4.2 / 2012-02-17 17 | 18 | * Support psych interface for YAML serialization. 19 | 20 | === 0.4.1 / 2011-04-26 21 | 22 | * Support underscore "_" as a delimiter. 23 | * A .gemspec file is now available for gem building and bundler git integration. 24 | * Some cleanup of the Rakefile and tests. 25 | 26 | === 0.4.0 / 2010-05-24 27 | 28 | * Included Semantic Version (http://semver.org/) format called "semver". 29 | * Parsing conversions can now prescreen or premodify the original format value. 30 | * 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. 31 | * Expanded the module version detection to include VERSION submodules. 32 | * Some Rakefile fixes to match RDoc and Ruby 1.9 changes. 33 | 34 | === 0.3.0 / 2009-11-30 35 | 36 | * Alpha release, opened for public feedback 37 | * Autoloading of format definitions using a load path. 38 | * Format and conversion registry/lookup is now thread-safe. 39 | * Implemented resetting a particular field in the value. 40 | * Implemented aliases for field names. 41 | * 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. 42 | * Documentation updates 43 | 44 | === 0.2.5 / 2009-11-24 45 | 46 | * Preserve minimum width of a field, if the field has leading zeros. For example, "2.01". 47 | 48 | === 0.2.4 / 2009-11-19 49 | 50 | * 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". 51 | 52 | === 0.2.3 / 2009-11-19 53 | 54 | * Recognize "_" and "u" as patchlevel delimiters, to support Sun's Java version numbers (e.g. "1.6.0_17", "6u17"). 55 | * Recognize "v" prefix found on some version numbers (e.g. "v1.2") 56 | 57 | === 0.2.2 / 2009-11-18 58 | 59 | * 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". 60 | * Added Versionomy#ruby_version. 61 | * A field can specify a default_value for parsing, distinct from the one specified by the schema. 62 | * A field can specify requires_next_field to control whether the following field is required or optional. 63 | 64 | === 0.2.1 / 2009-11-08 65 | 66 | * Added Versionomy#version_of. 67 | * Now lets Blockenspiel set its own VERSION instead of reaching into Blockenspiel's namespace. 68 | 69 | === 0.2.0 / 2009-11-05 70 | 71 | * 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. 72 | * API CHANGE: Merged Formats namespace into Format. Versionomy::Formats is now a (deprecated) alias for Versionomy::Format. 73 | * API CHANGE: The delimiter parsing algorithm now defaults :extra_characters to :error instead of :ignore. 74 | * Added a mechanism for converting from one format/schema to another. 75 | * Added Rubygems format, along with conversions to and from the standard format. 76 | * Values now include Comparable. 77 | * Values can now be serialized using Marshal and YAML. 78 | * Schemas can now add custom methods to value objects. Added "prerelease?" and "release" methods to rubygems and standard format values. 79 | * Added default field settings to schema DSL. 80 | * Implemented #=== for schemas and formats. 81 | * Many minor bug fixes and documentation updates. 82 | 83 | === 0.1.3 / 2009-10-29 84 | 85 | * Fixed an issue with parsing the "-p" patchlevel delimiter, e.g. "1.9.1-p243". (Reported by Luis Lavena.) 86 | 87 | === 0.1.2 / 2009-10-28 88 | 89 | * You can now specify fields by index in methods of Versionomy::Value. 90 | * Minor rakefile and documentation updates. 91 | 92 | === 0.1.1 / 2009-10-19 93 | 94 | * Formats can now be specified by name in the convenience interface. 95 | * FormatRedefinedError no longer subclasses FormatCreationError. 96 | * Some documentation updates. 97 | * Rakefile updates for publishing to rubyforge and gemcutter. 98 | 99 | === 0.1.0 / 2009-10-14 100 | 101 | * 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. 102 | * In the standard schema, renamed release type "release" to "final". Also renamed release type "prerelease" to "preview", now sorted between "release candidate" and "final". 103 | * Documentation is much more complete. 104 | * Now tested and confirmed compatible with Matz Ruby 1.9.1 and JRuby 1.4. 105 | * Now uses blockenspiel 0.2; thus longer requires the mixology gem. 106 | * Building no longer requires hoe. 107 | 108 | === 0.0.4 / 2008-10-24 109 | 110 | * Fixed incompatibility with Blockenspiel 0.0.4 111 | * Fixed a number of issues with remembering value parse settings 112 | * Parser recognizes additional release type formats 113 | * Values have a parse method to parse another string in the same form 114 | * Implemented comparison between value and string 115 | * Exceptions correctly raised on comparison between incompatible types 116 | 117 | === 0.0.3 / 2008-10-21 118 | 119 | * Fixed string representations (inspect method) 120 | * Fixed up equality and hash computation for version values 121 | 122 | === 0.0.2 / 2008-10-20 123 | 124 | * Fixed manifest 125 | 126 | === 0.0.1 / 2008-10-20 127 | 128 | * Initial test release 129 | -------------------------------------------------------------------------------- /lib/versionomy/conversion.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy conversion interface and registry 4 | # 5 | # ----------------------------------------------------------------------------- 6 | # Copyright 2008 Daniel Azuma 7 | # 8 | # All rights reserved. 9 | # 10 | # Redistribution and use in source and binary forms, with or without 11 | # modification, are permitted provided that the following conditions are met: 12 | # 13 | # * Redistributions of source code must retain the above copyright notice, 14 | # this list of conditions and the following disclaimer. 15 | # * Redistributions in binary form must reproduce the above copyright notice, 16 | # this list of conditions and the following disclaimer in the documentation 17 | # and/or other materials provided with the distribution. 18 | # * Neither the name of the copyright holder, nor the names of any other 19 | # contributors to this software, may be used to endorse or promote products 20 | # derived from this software without specific prior written permission. 21 | # 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 26 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | # POSSIBILITY OF SUCH DAMAGE. 33 | # ----------------------------------------------------------------------------- 34 | ; 35 | 36 | 37 | require 'thread' 38 | 39 | 40 | module Versionomy 41 | 42 | 43 | # === Conversion between version schemas. 44 | # 45 | # Conversions are algorithms for converting from one schema to another. 46 | # This is useful for performing conversions as well as comparing version 47 | # numbers that use different schemas. 48 | # 49 | # To implement a conversion algorithm, implement the API defined by 50 | # Versionomy::Conversion::Base. Then, register your conversion by calling 51 | # Versionomy::Conversion#register. You will need to specify which schemas 52 | # (from and to) that your conversion should handle. From that point on, 53 | # whenever Versionomy needs to convert a value between those two schemas, 54 | # it will use your conversion. You can register the same conversion object 55 | # for multiple pairs of schemas, but you can register only one conversion 56 | # object for any pair. 57 | # 58 | # A common technique for doing conversions is to unparse the version to a 59 | # string, and then parse it in the new format. Versionomy provides a tool, 60 | # Versionomy::Conversion::Parsing, for performing such conversions. The 61 | # conversions between the standard and rubygems formats uses this tool. 62 | # See Versionomy::Conversion::Rubygems for annotated examples. 63 | 64 | module Conversion 65 | 66 | @registry = {} 67 | @mutex = ::Mutex.new 68 | 69 | class << self 70 | 71 | 72 | # Convert the given value to the given format. This is identical to 73 | # calling value_.convert(format_, convert_params_). 74 | # 75 | # The format may be specified as a format object or as the name of a 76 | # format in the Format registry. 77 | # 78 | # Raises Versionomy::Errors::ConversionError if the value could not 79 | # be converted. 80 | 81 | def convert(value_, format_, convert_params_=nil) 82 | value_.convert(format_, convert_params_) 83 | end 84 | 85 | 86 | # Get a conversion capable of converting between the given schemas. 87 | # 88 | # The schemas may be specified as format names, Format objects, 89 | # schema wrapper objects, or the root field of the schema. 90 | # 91 | # If strict is set to false, returns nil if no such conversion could 92 | # be found. If strict is set to true, may raise one of these errors: 93 | # 94 | # Raises Versionomy::Errors::UnknownFormatError if a format was 95 | # specified by name but the name is not known. 96 | # 97 | # Raises Versionomy::Errors::UnknownConversionError if the formats 98 | # were recognized but no conversion was found to handle them. 99 | 100 | def get(from_schema_, to_schema_, strict_=false) 101 | key_ = _get_key(from_schema_, to_schema_) 102 | conversion_ = @mutex.synchronize{ @registry[key_] } 103 | if strict_ && conversion_.nil? 104 | raise Errors::UnknownConversionError 105 | end 106 | conversion_ 107 | end 108 | 109 | 110 | # Register the given conversion as the handler for the given schemas. 111 | # 112 | # The schemas may be specified as format names, Format objects, 113 | # schema wrapper objects, or the root field of the schema. 114 | # 115 | # Raises Versionomy::Errors::ConversionRedefinedError if a conversion 116 | # has already been registered for the given schemas. 117 | # 118 | # Raises Versionomy::Errors::UnknownFormatError if a format was 119 | # specified by name but the name is not known. 120 | 121 | def register(from_schema_, to_schema_, conversion_, silent_=false) 122 | key_ = _get_key(from_schema_, to_schema_) 123 | @mutex.synchronize do 124 | if @registry.include?(key_) 125 | unless silent_ 126 | raise Errors::ConversionRedefinedError 127 | end 128 | else 129 | @registry[key_] = conversion_ 130 | end 131 | end 132 | end 133 | 134 | 135 | private 136 | 137 | def _get_key(from_schema_, to_schema_) # :nodoc: 138 | [_get_schema(from_schema_), _get_schema(to_schema_)] 139 | end 140 | 141 | def _get_schema(schema_) # :nodoc: 142 | schema_ = Format.get(schema_, true) if schema_.kind_of?(::String) || schema_.kind_of?(::Symbol) 143 | schema_ = schema_.schema if schema_.respond_to?(:schema) 144 | schema_ = schema_.root_field if schema_.respond_to?(:root_field) 145 | schema_ 146 | end 147 | 148 | 149 | end 150 | 151 | end 152 | 153 | 154 | end 155 | -------------------------------------------------------------------------------- /test/tc_standard_bump.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy bump tests on standard schema 4 | # 5 | # This file contains tests for the bump function on the standard schema 6 | # 7 | # ----------------------------------------------------------------------------- 8 | # Copyright 2008 Daniel Azuma 9 | # 10 | # All rights reserved. 11 | # 12 | # Redistribution and use in source and binary forms, with or without 13 | # modification, are permitted provided that the following conditions are met: 14 | # 15 | # * Redistributions of source code must retain the above copyright notice, 16 | # this list of conditions and the following disclaimer. 17 | # * Redistributions in binary form must reproduce the above copyright notice, 18 | # this list of conditions and the following disclaimer in the documentation 19 | # and/or other materials provided with the distribution. 20 | # * Neither the name of the copyright holder, nor the names of any other 21 | # contributors to this software, may be used to endorse or promote products 22 | # derived from this software without specific prior written permission. 23 | # 24 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 28 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 | # POSSIBILITY OF SUCH DAMAGE. 35 | # ----------------------------------------------------------------------------- 36 | 37 | 38 | require 'minitest/autorun' 39 | require 'versionomy' 40 | 41 | 42 | module Versionomy 43 | module Tests # :nodoc: 44 | 45 | class TestStandardBump < ::Minitest::Test # :nodoc: 46 | 47 | 48 | # Test bumping a minor patchlevel. 49 | 50 | def test_bump_patchlevel_minor 51 | value_ = ::Versionomy.create(:major => 2, :tiny => 1, :patchlevel => 3, :patchlevel_minor => 0) 52 | value_ = value_.bump(:patchlevel_minor) 53 | assert_equal(2, value_.major) 54 | assert_equal(0, value_.minor) 55 | assert_equal(1, value_.tiny) 56 | assert_equal(0, value_.tiny2) 57 | assert_equal(:final, value_.release_type) 58 | assert_equal(3, value_.patchlevel) 59 | assert_equal(1, value_.patchlevel_minor) 60 | end 61 | 62 | 63 | # Test bumping a major patchlevel. 64 | 65 | def test_bump_patchlevel 66 | value_ = ::Versionomy.create(:major => 2, :tiny => 1, :patchlevel => 3, :patchlevel_minor => 1) 67 | value_ = value_.bump(:patchlevel) 68 | assert_equal(2, value_.major) 69 | assert_equal(0, value_.minor) 70 | assert_equal(1, value_.tiny) 71 | assert_equal(0, value_.tiny2) 72 | assert_equal(:final, value_.release_type) 73 | assert_equal(4, value_.patchlevel) 74 | assert_equal(0, value_.patchlevel_minor) 75 | end 76 | 77 | 78 | # Test bumping release type preview. 79 | 80 | def test_bump_preview_to_release 81 | value_ = ::Versionomy.create(:major => 2, :tiny => 1, :release_type => :preview) 82 | value_ = value_.bump(:release_type) 83 | assert_equal(2, value_.major) 84 | assert_equal(0, value_.minor) 85 | assert_equal(1, value_.tiny) 86 | assert_equal(0, value_.tiny2) 87 | assert_equal(:final, value_.release_type) 88 | assert_equal(0, value_.patchlevel) 89 | assert_equal(0, value_.patchlevel_minor) 90 | end 91 | 92 | 93 | # Test bumping release type development. 94 | 95 | def test_bump_development_to_alpha 96 | value_ = ::Versionomy.create(:major => 2, :tiny => 1, :release_type => :development, :development_version => 7) 97 | value_ = value_.bump(:release_type) 98 | assert_equal(2, value_.major) 99 | assert_equal(0, value_.minor) 100 | assert_equal(1, value_.tiny) 101 | assert_equal(0, value_.tiny2) 102 | assert_equal(:alpha, value_.release_type) 103 | assert_equal(1, value_.alpha_version) 104 | assert_equal(0, value_.alpha_minor) 105 | end 106 | 107 | 108 | # Test bumping release type alpha. 109 | 110 | def test_bump_alpha_to_beta 111 | value_ = ::Versionomy.create(:major => 2, :tiny => 1, :release_type => :alpha) 112 | value_ = value_.bump(:release_type) 113 | assert_equal(2, value_.major) 114 | assert_equal(0, value_.minor) 115 | assert_equal(1, value_.tiny) 116 | assert_equal(0, value_.tiny2) 117 | assert_equal(:beta, value_.release_type) 118 | assert_equal(1, value_.beta_version) 119 | assert_equal(0, value_.beta_minor) 120 | end 121 | 122 | 123 | # Test bumping release type release_candidate. 124 | 125 | def test_bump_release_candidate_to_release 126 | value_ = ::Versionomy.create(:major => 2, :tiny => 1, :release_type => :release_candidate, :release_candidate_version => 2) 127 | value_ = value_.bump(:release_type) 128 | assert_equal(2, value_.major) 129 | assert_equal(0, value_.minor) 130 | assert_equal(1, value_.tiny) 131 | assert_equal(0, value_.tiny2) 132 | assert_equal(:final, value_.release_type) 133 | assert_equal(0, value_.patchlevel) 134 | assert_equal(0, value_.patchlevel_minor) 135 | end 136 | 137 | 138 | # Test bumping tiny. 139 | 140 | def test_bump_tiny 141 | value_ = ::Versionomy.create(:major => 2, :tiny => 1, :tiny2 => 3, :release_type => :release_candidate, :release_candidate_version => 2) 142 | value_ = value_.bump(:tiny) 143 | assert_equal(2, value_.major) 144 | assert_equal(0, value_.minor) 145 | assert_equal(2, value_.tiny) 146 | assert_equal(0, value_.tiny2) 147 | assert_equal(:final, value_.release_type) 148 | assert_equal(0, value_.patchlevel) 149 | assert_equal(0, value_.patchlevel_minor) 150 | end 151 | 152 | 153 | # Test bumping major. 154 | 155 | def test_bump_major 156 | value_ = ::Versionomy.create(:major => 2, :tiny => 1, :tiny2 => 3, :release_type => :release_candidate, :release_candidate_version => 2) 157 | value_ = value_.bump(:major) 158 | assert_equal(3, value_.major) 159 | assert_equal(0, value_.minor) 160 | assert_equal(0, value_.tiny) 161 | assert_equal(0, value_.tiny2) 162 | assert_equal(:final, value_.release_type) 163 | assert_equal(0, value_.patchlevel) 164 | assert_equal(0, value_.patchlevel_minor) 165 | end 166 | 167 | 168 | end 169 | 170 | end 171 | end 172 | -------------------------------------------------------------------------------- /test/tc_standard_basic.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy basic tests on standard schema 4 | # 5 | # This file contains tests for the basic use cases on the standard schema 6 | # 7 | # ----------------------------------------------------------------------------- 8 | # Copyright 2008 Daniel Azuma 9 | # 10 | # All rights reserved. 11 | # 12 | # Redistribution and use in source and binary forms, with or without 13 | # modification, are permitted provided that the following conditions are met: 14 | # 15 | # * Redistributions of source code must retain the above copyright notice, 16 | # this list of conditions and the following disclaimer. 17 | # * Redistributions in binary form must reproduce the above copyright notice, 18 | # this list of conditions and the following disclaimer in the documentation 19 | # and/or other materials provided with the distribution. 20 | # * Neither the name of the copyright holder, nor the names of any other 21 | # contributors to this software, may be used to endorse or promote products 22 | # derived from this software without specific prior written permission. 23 | # 24 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 28 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 | # POSSIBILITY OF SUCH DAMAGE. 35 | # ----------------------------------------------------------------------------- 36 | 37 | 38 | require 'minitest/autorun' 39 | require 'versionomy' 40 | 41 | 42 | module Versionomy 43 | module Tests # :nodoc: 44 | 45 | class TestStandardBasic < ::Minitest::Test # :nodoc: 46 | 47 | 48 | # Test the default version value. 49 | 50 | def test_default_value 51 | value_ = ::Versionomy.create 52 | assert_equal(1, value_.major) 53 | assert_equal(0, value_.minor) 54 | assert_equal(0, value_.tiny) 55 | assert_equal(0, value_.tiny2) 56 | assert_equal(:final, value_.release_type) 57 | assert_equal(0, value_.patchlevel) 58 | assert_equal(0, value_.patchlevel_minor) 59 | end 60 | 61 | 62 | # Test an arbitrary release value. 63 | 64 | def test_release_value_1 65 | value_ = ::Versionomy.create(:major => 1, :tiny => 4, :tiny2 => 2, :patchlevel => 5) 66 | assert_equal(1, value_.major) 67 | assert_equal(0, value_.minor) 68 | assert_equal(4, value_.tiny) 69 | assert_equal(2, value_.tiny2) 70 | assert_equal(:final, value_.release_type) 71 | assert_equal(5, value_.patchlevel) 72 | assert_equal(0, value_.patchlevel_minor) 73 | assert_equal(false, value_.has_field?(:prerelase_version)) 74 | assert_equal(false, value_.has_field?(:prerelase_minor)) 75 | end 76 | 77 | 78 | # Test an arbitrary release value. 79 | 80 | def test_release_value_2 81 | value_ = ::Versionomy.create(:major => 0, :minor => 3) 82 | assert_equal(0, value_.major) 83 | assert_equal(3, value_.minor) 84 | assert_equal(0, value_.tiny) 85 | assert_equal(0, value_.tiny2) 86 | assert_equal(:final, value_.release_type) 87 | assert_equal(0, value_.patchlevel) 88 | assert_equal(0, value_.patchlevel_minor) 89 | assert_equal(false, value_.has_field?(:prerelase_version)) 90 | assert_equal(false, value_.has_field?(:prerelase_minor)) 91 | end 92 | 93 | 94 | # Test an arbitrary preview value. 95 | 96 | def test_preview_value_1 97 | value_ = ::Versionomy.create(:major => 2, :minor => 3, :release_type => :preview, :preview_version => 3) 98 | assert_equal(2, value_.major) 99 | assert_equal(3, value_.minor) 100 | assert_equal(0, value_.tiny) 101 | assert_equal(0, value_.tiny2) 102 | assert_equal(:preview, value_.release_type) 103 | assert_equal(3, value_.preview_version) 104 | assert_equal(0, value_.preview_minor) 105 | assert_equal(false, value_.has_field?(:patchlevel)) 106 | assert_equal(false, value_.has_field?(:patchlevel_minor)) 107 | end 108 | 109 | 110 | # Test an arbitrary preview value. 111 | 112 | def test_preview_value_2 113 | value_ = ::Versionomy.create(:major => 2, :minor => 3, :release_type => :preview) 114 | assert_equal(2, value_.major) 115 | assert_equal(3, value_.minor) 116 | assert_equal(0, value_.tiny) 117 | assert_equal(0, value_.tiny2) 118 | assert_equal(:preview, value_.release_type) 119 | assert_equal(1, value_.preview_version) 120 | assert_equal(0, value_.preview_minor) 121 | assert_equal(false, value_.has_field?(:patchlevel)) 122 | assert_equal(false, value_.has_field?(:patchlevel_minor)) 123 | end 124 | 125 | 126 | # Test an arbitrary beta value. 127 | 128 | def test_beta_value 129 | value_ = ::Versionomy.create(:major => 2, :tiny => 1, :release_type => :beta, :beta_version => 3) 130 | assert_equal(2, value_.major) 131 | assert_equal(0, value_.minor) 132 | assert_equal(1, value_.tiny) 133 | assert_equal(0, value_.tiny2) 134 | assert_equal(:beta, value_.release_type) 135 | assert_equal(3, value_.beta_version) 136 | assert_equal(0, value_.beta_minor) 137 | assert_equal(false, value_.has_field?(:prerelase_version)) 138 | assert_equal(false, value_.has_field?(:prerelase_minor)) 139 | assert_equal(false, value_.has_field?(:patchlevel)) 140 | assert_equal(false, value_.has_field?(:patchlevel_minor)) 141 | end 142 | 143 | 144 | # Test specifying fields by index. 145 | 146 | def test_field_get_index 147 | value_ = ::Versionomy.create(:major => 2, :tiny => 1, :release_type => :beta, :beta_version => 3) 148 | assert_equal(2, value_[0]) 149 | assert_equal(0, value_[1]) 150 | assert_equal(1, value_[2]) 151 | assert_equal(0, value_[3]) 152 | assert_equal(:beta, value_[4]) 153 | assert_equal(3, value_[5]) 154 | assert_equal(0, value_[6]) 155 | end 156 | 157 | 158 | # Test specifying fields by name. 159 | 160 | def test_field_get_name 161 | value_ = ::Versionomy.create(:major => 2, :tiny => 1, :release_type => :beta, :beta_version => 3) 162 | assert_equal(2, value_[:major]) 163 | assert_equal(0, value_[:minor]) 164 | assert_equal(1, value_[:tiny]) 165 | assert_equal(0, value_[:tiny2]) 166 | assert_equal(:beta, value_[:release_type]) 167 | assert_equal(3, value_[:beta_version]) 168 | assert_equal(0, value_[:beta_minor]) 169 | end 170 | 171 | 172 | end 173 | 174 | end 175 | end 176 | -------------------------------------------------------------------------------- /test/tc_semver_basic.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy basic tests on rubygems schema 4 | # 5 | # This file contains tests for the basic use cases on the rubygems schema 6 | # 7 | # ----------------------------------------------------------------------------- 8 | # Copyright 2008 Daniel Azuma 9 | # 10 | # All rights reserved. 11 | # 12 | # Redistribution and use in source and binary forms, with or without 13 | # modification, are permitted provided that the following conditions are met: 14 | # 15 | # * Redistributions of source code must retain the above copyright notice, 16 | # this list of conditions and the following disclaimer. 17 | # * Redistributions in binary form must reproduce the above copyright notice, 18 | # this list of conditions and the following disclaimer in the documentation 19 | # and/or other materials provided with the distribution. 20 | # * Neither the name of the copyright holder, nor the names of any other 21 | # contributors to this software, may be used to endorse or promote products 22 | # derived from this software without specific prior written permission. 23 | # 24 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 28 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 | # POSSIBILITY OF SUCH DAMAGE. 35 | # ----------------------------------------------------------------------------- 36 | 37 | 38 | require 'minitest/autorun' 39 | require 'versionomy' 40 | require 'yaml' 41 | 42 | module Versionomy 43 | module Tests # :nodoc: 44 | 45 | class TestSemverBasic < ::Minitest::Test # :nodoc: 46 | 47 | 48 | # Test the default version value. 49 | 50 | def test_default_value 51 | value_ = ::Versionomy.create(nil, :semver) 52 | assert_equal(1, value_.major) 53 | assert_equal(0, value_.minor) 54 | assert_equal(0, value_.patch) 55 | assert_equal('', value_.prerelease_suffix) 56 | end 57 | 58 | 59 | # Test an arbitrary value. 60 | 61 | def test_arbitrary_value 62 | value_ = ::Versionomy.create([1, 9, 2, 'pre2'], :semver) 63 | assert_equal(1, value_.major) 64 | assert_equal(9, value_.minor) 65 | assert_equal(2, value_.patch) 66 | assert_equal('pre2', value_.prerelease_suffix) 67 | end 68 | 69 | 70 | # Test aliases 71 | 72 | def test_alias_fields 73 | value_ = ::Versionomy.create([1, 9, 2, 'pre2'], :semver) 74 | assert_equal('pre2', value_.special_suffix) 75 | end 76 | 77 | 78 | # Test construction using aliases 79 | 80 | def test_alias_field_construction 81 | value_ = ::Versionomy.create({:major => 1, :minor => 9, :special_suffix => 'pre2'}, :semver) 82 | assert_equal([1, 9, 0, 'pre2'], value_.values_array) 83 | end 84 | 85 | 86 | # Test comparison of numeric values. 87 | 88 | def test_numeric_comparison 89 | value1_ = ::Versionomy.create([1, 9, 2], :semver) 90 | value2_ = ::Versionomy.create([1, 9], :semver) 91 | assert(value2_ < value1_) 92 | value1_ = ::Versionomy.create([1, 9, 0], :semver) 93 | value2_ = ::Versionomy.create([1, 9], :semver) 94 | assert(value2_ == value1_) 95 | end 96 | 97 | 98 | # Test comparison of string values. 99 | 100 | def test_string_comparison 101 | value1_ = ::Versionomy.create([1, 9, 2, 'a2'], :semver) 102 | value2_ = ::Versionomy.create([1, 9, 2, 'b1'], :semver) 103 | assert(value2_ > value1_) 104 | end 105 | 106 | 107 | # Test comparison of numeric and string values. 108 | 109 | def test_numeric_and_string_comparison 110 | value1_ = ::Versionomy.create([1, 9, 2, 'a2'], :semver) 111 | value2_ = ::Versionomy.create([1, 9, 2], :semver) 112 | assert(value2_ > value1_) 113 | end 114 | 115 | 116 | # Test parsing numeric. 117 | 118 | def test_parsing_numeric 119 | value_ = ::Versionomy.parse('2.0.1', :semver) 120 | assert_equal([2, 0, 1, ''], value_.values_array) 121 | assert_equal('2.0.1', value_.unparse) 122 | end 123 | 124 | 125 | # Test parsing with a string. 126 | 127 | def test_parsing_with_string 128 | value_ = ::Versionomy.parse('1.9.2pre2', :semver) 129 | assert_equal([1, 9, 2, 'pre2'], value_.values_array) 130 | assert_equal('1.9.2pre2', value_.unparse) 131 | end 132 | 133 | 134 | # Test making sure unparsing requires all three fields. 135 | 136 | def test_parsing_require_all_fields 137 | value_ = ::Versionomy.parse('2.0', :semver) 138 | assert_equal([2, 0, 0, ''], value_.values_array) 139 | assert_equal('2.0.0', value_.unparse) 140 | assert_raises(::Versionomy::Errors::ParseError) do 141 | value_ = ::Versionomy.parse('2.0b1', :semver) 142 | end 143 | end 144 | 145 | 146 | # Test convenience parsing 147 | 148 | def test_convenience_parse 149 | value_ = ::Versionomy.semver('2.0.1') 150 | assert_equal([2, 0, 1, ''], value_.values_array) 151 | end 152 | 153 | 154 | # Test convenience creation from hash 155 | 156 | def test_convenience_create 157 | value_ = ::Versionomy.semver(:major => 2, :patch => 1, :prerelease_suffix => 'b2') 158 | assert_equal([2, 0, 1, 'b2'], value_.values_array) 159 | end 160 | 161 | 162 | # Test bumping a numeric field. 163 | 164 | def test_bump_numeric 165 | value_ = ::Versionomy.create([1, 9, 2, 'a2'], :semver) 166 | value_ = value_.bump(:patch) 167 | assert_equal([1, 9, 3, ''], value_.values_array) 168 | end 169 | 170 | 171 | # Test bumping a string field. 172 | 173 | def test_bump_string 174 | value_ = ::Versionomy.create([1, 9, 2, 'a2'], :semver) 175 | value_ = value_.bump(:prerelease_suffix) 176 | assert_equal([1, 9, 2, 'a3'], value_.values_array) 177 | end 178 | 179 | 180 | # Test "prerelase?" custom method 181 | 182 | def test_method_prereleasep 183 | value_ = ::Versionomy.create([1, 9, 2, 'a2'], :semver) 184 | assert_equal(true, value_.prerelease?) 185 | value_ = ::Versionomy.create([1, 9, 2], :semver) 186 | assert_equal(false, value_.prerelease?) 187 | end 188 | 189 | 190 | # Test marshalling 191 | 192 | def test_marshal 193 | value_ = ::Versionomy.create([1, 9, 2, 'pre2'], :semver) 194 | str_ = ::Marshal.dump(value_) 195 | value2_ = ::Marshal.load(str_) 196 | assert_equal(value_, value2_) 197 | end 198 | 199 | 200 | # Test YAML 201 | 202 | def test_yaml 203 | value_ = ::Versionomy.create([1, 9, 2, 'pre2'], :semver) 204 | str_ = ::YAML.dump(value_) 205 | value2_ = ::YAML.load(str_) 206 | assert_equal(value_, value2_) 207 | end 208 | 209 | 210 | end 211 | 212 | end 213 | end 214 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | == Versionomy 2 | 3 | Versionomy is a generalized version number library. 4 | It provides tools to represent, manipulate, parse, and compare version 5 | numbers in the wide variety of versioning schemes in use. 6 | 7 | This document summarizes the features of Versionomy with a quick synopsis 8 | and feature list. For more detailed usage information and examples, see 9 | {Versionomy.rdoc}[link:Versionomy\_rdoc.html]. 10 | 11 | === Some examples 12 | 13 | require 'versionomy' 14 | 15 | # Create version numbers that understand their own semantics 16 | v1 = Versionomy.create(:major => 1, :minor => 3, :tiny => 2) 17 | v1.major # => 1 18 | v1.minor # => 3 19 | v1.tiny # => 2 20 | v1.release_type # => :final 21 | v1.patchlevel # => 0 22 | 23 | # Parse version numbers, including common prerelease syntax 24 | v2 = Versionomy.parse('1.4a3') 25 | v2.major # => 1 26 | v2.minor # => 4 27 | v2.tiny # => 0 28 | v2.release_type # => :alpha 29 | v2.alpha_version # => 3 30 | v2 > v1 # => true 31 | v2.to_s # => '1.4a3' 32 | 33 | # Version numbers are semantically self-adjusting. 34 | v3 = Versionomy.parse('1.4.0b2') 35 | v3.major # => 1 36 | v3.minor # => 4 37 | v3.tiny # => 0 38 | v3.release_type # => :beta 39 | v3.alpha_version # raises NoMethodError 40 | v3.beta_version # => 2 41 | v3 > v2 # => true 42 | v3.to_s # => '1.4.0b2' 43 | 44 | # You can bump any field 45 | v4 = Versionomy.parse('1.4.0b2').bump(:beta_version) 46 | v4.to_s # => '1.4.0b3' 47 | v5 = v4.bump(:tiny) 48 | v5.to_s # => '1.4.1' 49 | 50 | # Bumping the release type works as you would expect 51 | v6 = Versionomy.parse('1.4.0b2').bump(:release_type) 52 | v6.release_type # => :release_candidate 53 | v6.to_s # => '1.4.0rc1' 54 | v7 = v6.bump(:release_type) 55 | v7.release_type # => :final 56 | v7.to_s # => '1.4.0' 57 | 58 | # If a version has trailing zeros, it remembers how many fields to 59 | # unparse; however, you can also change this. 60 | v8 = Versionomy.parse('1.4.0b2').bump(:major) 61 | v8.to_s # => '2.0.0' 62 | v8.unparse(:optional_fields => [:tiny]) # => '2.0' 63 | v8.unparse(:required_fields => [:tiny2]) # => '2.0.0.0' 64 | 65 | # Comparisons are semantic, so will behave as expected even if the 66 | # formatting is set up differently. 67 | v9 = Versionomy.parse('2.0.0.0') 68 | v9.to_s # => '2.0.0.0' 69 | v9 == Versionomy.parse('2') # => true 70 | 71 | # Patchlevels are supported when the release type is :final 72 | v10 = Versionomy.parse('2.0.0').bump(:patchlevel) 73 | v10.patchlevel # => 1 74 | v10.to_s # => '2.0.0-1' 75 | v11 = Versionomy.parse('2.0p1') 76 | v11.patchlevel # => 1 77 | v11.to_s # => '2.0p1' 78 | v11 == v10 # => true 79 | 80 | # You can create your own format from scratch or by modifying an 81 | # existing format 82 | microsoft_format = Versionomy.default_format.modified_copy do 83 | field(:minor) do 84 | recognize_number(:default_value_optional => true, 85 | :delimiter_regexp => '\s?sp', 86 | :default_delimiter => ' SP') 87 | end 88 | end 89 | v12 = microsoft_format.parse('2008 SP2') 90 | v12.major # => 2008 91 | v12.minor # => 2 92 | v12.tiny # => 0 93 | v12.to_s # => '2008 SP2' 94 | v12 == Versionomy.parse('2008.2') # => true 95 | 96 | === Feature list 97 | 98 | Versionomy's default versioning scheme handles four primary fields (labeled 99 | +major+, +minor+, +tiny+, and +tiny2+). It also supports prerelease versions 100 | such as preview, development, alpha, beta, and release candidate. Finally, 101 | it supports patchlevel numbers for released versions. 102 | 103 | Versionomy can compare any two version numbers with compatible structure, 104 | and "bump" versions at any level. It supports parsing and unparsing in most 105 | commonly-used formats, and allows you to extend the parsing to include 106 | custom formats. 107 | 108 | Finally, Versionomy also lets you to create alternate versioning "schemas". 109 | You can define any number of version number fields, and provide your own 110 | semantics for comparing, parsing, and modifying version numbers. You can 111 | provide conversions from one schema to another. As an example, Versionomy 112 | provides a schema and formatter/parser matching Gem::Version. 113 | 114 | === Requirements 115 | 116 | * Ruby 1.9.3 or later, JRuby 1.5 or later, or Rubinius 1.0 or later. 117 | * blockenspiel 0.5.0 or later. 118 | 119 | === Installation 120 | 121 | gem install versionomy 122 | 123 | === Known issues and limitations 124 | 125 | * Test coverage is still a little skimpy. It is focused on the "standard" 126 | version number format and schema, but doesn't fully exercise all the 127 | capabilities of custom formats. 128 | 129 | === Development and support 130 | 131 | Documentation is available at http://dazuma.github.com/versionomy/rdoc 132 | 133 | Source code is hosted on Github at http://github.com/dazuma/versionomy 134 | 135 | Contributions are welcome. Fork the project on Github. 136 | 137 | Build status: {}[http://travis-ci.org/dazuma/versionomy] 138 | 139 | Report bugs on Github issues at http://github.org/dazuma/versionomy/issues 140 | 141 | Contact the author at dazuma at gmail dot com. 142 | 143 | === Author / Credits 144 | 145 | Versionomy is written by Daniel Azuma (http://www.daniel-azuma.com/). 146 | 147 | == LICENSE: 148 | 149 | Copyright 2008 Daniel Azuma. 150 | 151 | All rights reserved. 152 | 153 | Redistribution and use in source and binary forms, with or without 154 | modification, are permitted provided that the following conditions are met: 155 | 156 | * Redistributions of source code must retain the above copyright notice, 157 | this list of conditions and the following disclaimer. 158 | * Redistributions in binary form must reproduce the above copyright notice, 159 | this list of conditions and the following disclaimer in the documentation 160 | and/or other materials provided with the distribution. 161 | * Neither the name of the copyright holder, nor the names of any other 162 | contributors to this software, may be used to endorse or promote products 163 | derived from this software without specific prior written permission. 164 | 165 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 166 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 167 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 168 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 169 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 170 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 171 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 172 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 173 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 174 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 175 | POSSIBILITY OF SUCH DAMAGE. 176 | -------------------------------------------------------------------------------- /lib/versionomy/conversion/parsing.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy conversion base class 4 | # 5 | # ----------------------------------------------------------------------------- 6 | # Copyright 2008 Daniel Azuma 7 | # 8 | # All rights reserved. 9 | # 10 | # Redistribution and use in source and binary forms, with or without 11 | # modification, are permitted provided that the following conditions are met: 12 | # 13 | # * Redistributions of source code must retain the above copyright notice, 14 | # this list of conditions and the following disclaimer. 15 | # * Redistributions in binary form must reproduce the above copyright notice, 16 | # this list of conditions and the following disclaimer in the documentation 17 | # and/or other materials provided with the distribution. 18 | # * Neither the name of the copyright holder, nor the names of any other 19 | # contributors to this software, may be used to endorse or promote products 20 | # derived from this software without specific prior written permission. 21 | # 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 26 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | # POSSIBILITY OF SUCH DAMAGE. 33 | # ----------------------------------------------------------------------------- 34 | ; 35 | 36 | 37 | module Versionomy 38 | 39 | module Conversion 40 | 41 | 42 | # A conversion strategy that relies on parsing. 43 | # Essentially, it unparses the value and then attempts to parse it with 44 | # the new format. 45 | 46 | class Parsing < Base 47 | 48 | 49 | # Create a parsing conversion. 50 | # 51 | # By default, this just unparses and reparses using the default 52 | # parse settings. In some cases, this may be enough, but you may 53 | # wish to improve the reliability of the conversion by tweaking the 54 | # parsing settings. To do so, pass a block to the new method, and 55 | # call methods of Versionomy::Conversion::Parsing::Builder in that 56 | # block. 57 | 58 | def initialize(&block_) 59 | if block_ 60 | builder_ = Builder.new 61 | ::Blockenspiel.invoke(block_, builder_) 62 | @original_value_modifier = builder_._get_original_value_modifier 63 | @string_modifier = builder_._get_string_modifier 64 | @unparse_params_modifier = builder_._get_unparse_params_modifier 65 | @parse_params_generator ||= builder_._get_parse_params_generator 66 | end 67 | end 68 | 69 | 70 | # Returns a value equivalent to the given value in the given format. 71 | # 72 | # The convert_params are passed to this conversion's customization 73 | # blocks (if any). 74 | # 75 | # Raises Versionomy::Errors::ConversionError if the conversion failed. 76 | # Typically, this is due to a failure of the parsing or unparsing. 77 | 78 | def convert_value(value_, format_, convert_params_=nil) 79 | begin 80 | convert_params_ ||= {} 81 | if @original_value_modifier 82 | value_ = @original_value_modifier.call(value_, convert_params_) 83 | end 84 | unparse_params_ = value_.unparse_params || {} 85 | if @unparse_params_modifier 86 | unparse_params_ = @unparse_params_modifier.call(unparse_params_, convert_params_) 87 | end 88 | string_ = value_.unparse(unparse_params_) 89 | if @string_modifier 90 | string_ = @string_modifier.call(string_, convert_params_) 91 | end 92 | if @parse_params_generator 93 | parse_params_ = @parse_params_generator.call(convert_params_) 94 | else 95 | parse_params_ = nil 96 | end 97 | new_value_ = format_.parse(string_, parse_params_) 98 | return new_value_ 99 | rescue Errors::UnparseError => ex_ 100 | raise Errors::ConversionError, "Unparsing failed: #{ex_.inspect}" 101 | rescue Errors::ParseError => ex_ 102 | raise Errors::ConversionError, "Parsing failed: #{ex_.inspect}" 103 | end 104 | end 105 | 106 | 107 | # Call methods of this class in the block passed to 108 | # Versionomy::Conversion::Parsing#new to fine-tune the behavior of 109 | # the converter. 110 | 111 | class Builder 112 | 113 | include ::Blockenspiel::DSL 114 | 115 | 116 | def initialize # :nodoc: 117 | @original_value_modifier = nil 118 | @string_modifier = nil 119 | @parse_params_generator = nil 120 | @unparse_params_modifier = nil 121 | end 122 | 123 | 124 | # Provide a block that generates the params used to parse the new 125 | # value. The block should take one parameter, the convert_params 126 | # passed to convert_value (which may be nil). It should return the 127 | # parse params that should be used. 128 | 129 | def to_generate_parse_params(&block_) 130 | @parse_params_generator = block_ 131 | end 132 | 133 | 134 | # Provide a block that can modify the params used to unparse the 135 | # old value. The block should take two parameters: first, the 136 | # original unparse params from the old value (which may be nil), 137 | # and second, the convert_params passed to convert_value (which 138 | # may also be nil). It should return the unparse params that 139 | # should actually be used. 140 | 141 | def to_modify_unparse_params(&block_) 142 | @unparse_params_modifier = block_ 143 | end 144 | 145 | 146 | # Provide a block that can modify the original value prior to it 147 | # being unparsed. The block should take two parameters: first, the 148 | # original value to be converted, and second, the convert_params 149 | # passed to convert_value (which may be nil). It should return the 150 | # value to be unparsed, which may be the same as the value 151 | # originally passed in. This method may fail-fast by raising a 152 | # Versionomy::Errors::ConversionError if it determines that the 153 | # value passed in cannot be converted as is. 154 | 155 | def to_modify_original_value(&block_) 156 | @original_value_modifier = block_ 157 | end 158 | 159 | 160 | # Provide a block that can modify the unparsed string prior to 161 | # it being passed to the parser. The block should take two 162 | # parameters: first, the string resulting from unparsing the old 163 | # value, and second, the convert_params passed to convert_value 164 | # (which may be nil). It should return the string to be parsed to 165 | # get the new value. 166 | 167 | def to_modify_string(&block_) 168 | @string_modifier = block_ 169 | end 170 | 171 | 172 | def _get_original_value_modifier # :nodoc: 173 | @original_value_modifier 174 | end 175 | 176 | def _get_string_modifier # :nodoc: 177 | @string_modifier 178 | end 179 | 180 | def _get_unparse_params_modifier # :nodoc: 181 | @unparse_params_modifier 182 | end 183 | 184 | def _get_parse_params_generator # :nodoc: 185 | @parse_params_generator 186 | end 187 | 188 | end 189 | 190 | 191 | end 192 | 193 | 194 | end 195 | 196 | end 197 | -------------------------------------------------------------------------------- /test/tc_semver_conversions.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy tests on rubygems schema conversions 4 | # 5 | # This file contains tests converting to and from the rubygems schema 6 | # 7 | # ----------------------------------------------------------------------------- 8 | # Copyright 2008 Daniel Azuma 9 | # 10 | # All rights reserved. 11 | # 12 | # Redistribution and use in source and binary forms, with or without 13 | # modification, are permitted provided that the following conditions are met: 14 | # 15 | # * Redistributions of source code must retain the above copyright notice, 16 | # this list of conditions and the following disclaimer. 17 | # * Redistributions in binary form must reproduce the above copyright notice, 18 | # this list of conditions and the following disclaimer in the documentation 19 | # and/or other materials provided with the distribution. 20 | # * Neither the name of the copyright holder, nor the names of any other 21 | # contributors to this software, may be used to endorse or promote products 22 | # derived from this software without specific prior written permission. 23 | # 24 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 28 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 | # POSSIBILITY OF SUCH DAMAGE. 35 | # ----------------------------------------------------------------------------- 36 | 37 | 38 | require 'minitest/autorun' 39 | require 'versionomy' 40 | 41 | 42 | module Versionomy 43 | module Tests # :nodoc: 44 | 45 | class TestSemverConversions < ::Minitest::Test # :nodoc: 46 | 47 | 48 | def setup 49 | @standard_format = Format.get(:standard) 50 | @semver_format = Format.get(:semver) 51 | end 52 | 53 | 54 | # Test simple conversion from standard to semver. 55 | 56 | def test_standard_to_semver_simple 57 | value_ = ::Versionomy.parse('1.2') 58 | value2_ = value_.convert(:semver) 59 | assert_equal(@semver_format, value2_.format) 60 | assert_equal([1, 2, 0, ''], value2_.values_array) 61 | value_ = ::Versionomy.parse('1.2.4') 62 | value2_ = value_.convert(:semver) 63 | assert_equal(@semver_format, value2_.format) 64 | assert_equal([1, 2, 4, ''], value2_.values_array) 65 | end 66 | 67 | 68 | # Test conversion from standard to semver with a beta version 69 | 70 | def test_standard_to_semver_beta 71 | value_ = ::Versionomy.parse('1.2b3') 72 | value2_ = value_.convert(:semver) 73 | assert_equal([1, 2, 0, 'b3'], value2_.values_array) 74 | value_ = ::Versionomy.parse('1.2 beta 3') 75 | value2_ = value_.convert(:semver) 76 | assert_equal([1, 2, 0, 'beta3'], value2_.values_array) 77 | end 78 | 79 | 80 | # Test conversion from standard to semver with a "v" prefix 81 | 82 | def test_standard_to_semver_with_v 83 | value_ = ::Versionomy.parse('v1.2.3') 84 | value2_ = value_.convert(:semver) 85 | assert_equal([1, 2, 3, ''], value2_.values_array) 86 | value_ = ::Versionomy.parse('V 1.2.3') 87 | value2_ = value_.convert(:semver) 88 | assert_equal([1, 2, 3, ''], value2_.values_array) 89 | end 90 | 91 | 92 | # Test conversion from standard to semver with an expectation of failure 93 | 94 | def test_standard_to_semver_fail 95 | value_ = ::Versionomy.parse('1.2.3.4', :standard) 96 | assert_raises(::Versionomy::Errors::ConversionError) do 97 | value_.convert(:semver) 98 | end 99 | end 100 | 101 | 102 | # Test simple conversion from semver to standard. 103 | 104 | def test_semver_to_standard_simple 105 | value_ = ::Versionomy.parse('1.2', :semver) 106 | value2_ = value_.convert(:standard) 107 | assert_equal(@standard_format, value2_.format) 108 | assert_equal([1, 2, 0, 0, :final, 0, 0], value2_.values_array) 109 | value_ = ::Versionomy.parse('1.2.4', :semver) 110 | value2_ = value_.convert(:standard) 111 | assert_equal(@standard_format, value2_.format) 112 | assert_equal([1, 2, 4, 0, :final, 0, 0], value2_.values_array) 113 | end 114 | 115 | 116 | # Test conversion from semver to standard with a beta version 117 | 118 | def test_semver_to_standard_beta 119 | value_ = ::Versionomy.parse('1.2.0b3', :semver) 120 | value2_ = value_.convert(:standard) 121 | assert_equal([1, 2, 0, 0, :beta, 3, 0], value2_.values_array) 122 | value_ = ::Versionomy.parse('1.2.4beta3', :semver) 123 | value2_ = value_.convert(:standard) 124 | assert_equal([1, 2, 4, 0, :beta, 3, 0], value2_.values_array) 125 | end 126 | 127 | 128 | # Test conversion from rubygems to standard with an expectation of failure 129 | 130 | def test_semver_to_standard_fail 131 | value_ = ::Versionomy.parse('1.2.3c4', :semver) 132 | assert_raises(::Versionomy::Errors::ConversionError) do 133 | value_.convert(:standard) 134 | end 135 | end 136 | 137 | 138 | # Test conversion when there aren't unparse_params 139 | 140 | def test_standard_to_semver_without_unparse_params 141 | value_ = ::Versionomy.create([1,2,3], :standard) 142 | value2_ = value_.convert(:semver) 143 | assert_equal([1, 2, 3, ''], value2_.values_array) 144 | end 145 | 146 | 147 | # Test conversion between semver and rubygems 148 | 149 | def test_semver_and_rubygems 150 | value_ = ::Versionomy.create([1,2,3], :semver) 151 | value2_ = value_.convert(:rubygems) 152 | assert_equal([1, 2, 3, 0, 0, 0, 0, 0], value2_.values_array) 153 | value_ = ::Versionomy.create([1,2,3], :rubygems) 154 | value2_ = value_.convert(:semver) 155 | assert_equal([1, 2, 3, ''], value2_.values_array) 156 | end 157 | 158 | 159 | # Test equality comparisons between semver and standard 160 | 161 | def test_semver_to_standard_equality_comparison 162 | assert_operator(::Versionomy.parse('1.2.0', :semver), :==, ::Versionomy.parse('1.2')) 163 | assert_operator(::Versionomy.parse('1.2.0b3', :semver), :==, ::Versionomy.parse('1.2b3')) 164 | end 165 | 166 | 167 | # Test inequality comparisons between semver and standard 168 | 169 | def test_semver_to_standard_inequality_comparison 170 | assert_operator(::Versionomy.parse('1.2.3', :semver), :<, ::Versionomy.parse('1.2.4')) 171 | assert_operator(::Versionomy.parse('1.2.0b3', :semver), :>, ::Versionomy.parse('1.2b2')) 172 | assert_operator(::Versionomy.parse('1.2.0', :semver), :>, ::Versionomy.parse('1.2b1')) 173 | end 174 | 175 | 176 | # Test equality comparisons between standard and rubygems 177 | 178 | def test_standard_to_semver_equality_comparison 179 | assert_operator(::Versionomy.parse('1.2.0'), :==, ::Versionomy.parse('1.2.0', :semver)) 180 | assert_operator(::Versionomy.parse('1.2b3'), :==, ::Versionomy.parse('1.2.0beta3', :semver)) 181 | end 182 | 183 | 184 | # Test inequality comparisons between standard and rubygems 185 | 186 | def test_standard_to_semver_inequality_comparison 187 | assert_operator(::Versionomy.parse('1.2.4'), :>, ::Versionomy.parse('1.2.3', :semver)) 188 | assert_operator(::Versionomy.parse('1.2b2'), :<, ::Versionomy.parse('1.2.0beta3', :semver)) 189 | assert_operator(::Versionomy.parse('1.2b2'), :<, ::Versionomy.parse('1.2.0', :semver)) 190 | end 191 | 192 | 193 | end 194 | 195 | end 196 | end 197 | -------------------------------------------------------------------------------- /test/tc_rubygems_conversions.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy tests on rubygems schema conversions 4 | # 5 | # This file contains tests converting to and from the rubygems schema 6 | # 7 | # ----------------------------------------------------------------------------- 8 | # Copyright 2008 Daniel Azuma 9 | # 10 | # All rights reserved. 11 | # 12 | # Redistribution and use in source and binary forms, with or without 13 | # modification, are permitted provided that the following conditions are met: 14 | # 15 | # * Redistributions of source code must retain the above copyright notice, 16 | # this list of conditions and the following disclaimer. 17 | # * Redistributions in binary form must reproduce the above copyright notice, 18 | # this list of conditions and the following disclaimer in the documentation 19 | # and/or other materials provided with the distribution. 20 | # * Neither the name of the copyright holder, nor the names of any other 21 | # contributors to this software, may be used to endorse or promote products 22 | # derived from this software without specific prior written permission. 23 | # 24 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 28 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 | # POSSIBILITY OF SUCH DAMAGE. 35 | # ----------------------------------------------------------------------------- 36 | 37 | 38 | require 'minitest/autorun' 39 | require 'versionomy' 40 | 41 | 42 | module Versionomy 43 | module Tests # :nodoc: 44 | 45 | class TestRubygemsConversions < ::Minitest::Test # :nodoc: 46 | 47 | 48 | def setup 49 | @standard_format = Format.get(:standard) 50 | @rubygems_format = Format.get(:rubygems) 51 | end 52 | 53 | 54 | # Test simple conversion from standard to rubygems. 55 | 56 | def test_standard_to_rubygems_simple 57 | value_ = ::Versionomy.parse('1.2') 58 | value2_ = value_.convert(:rubygems) 59 | assert_equal(@rubygems_format, value2_.format) 60 | assert_equal([1, 2, 0, 0, 0, 0, 0, 0], value2_.values_array) 61 | value_ = ::Versionomy.parse('1.2.4.1') 62 | value2_ = value_.convert(:rubygems) 63 | assert_equal(@rubygems_format, value2_.format) 64 | assert_equal([1, 2, 4, 1, 0, 0, 0, 0], value2_.values_array) 65 | end 66 | 67 | 68 | # Test conversion from standard to rubygems including a patchlevel 69 | 70 | def test_standard_to_rubygems_with_patchlevel 71 | value_ = ::Versionomy.parse('1.2-3') 72 | value2_ = value_.convert(:rubygems) 73 | assert_equal([1, 2, 3, 0, 0, 0, 0, 0], value2_.values_array) 74 | value_ = ::Versionomy.parse('1.2p3') 75 | value2_ = value_.convert(:rubygems) 76 | assert_equal([1, 2, 3, 0, 0, 0, 0, 0], value2_.values_array) 77 | value_ = ::Versionomy.parse('1.2c') 78 | value2_ = value_.convert(:rubygems) 79 | assert_equal([1, 2, 3, 0, 0, 0, 0, 0], value2_.values_array) 80 | end 81 | 82 | 83 | # Test conversion from standard to rubygems with a beta version 84 | 85 | def test_standard_to_rubygems_beta 86 | value_ = ::Versionomy.parse('1.2b3') 87 | value2_ = value_.convert(:rubygems) 88 | assert_equal([1, 2, 'b', 3, 0, 0, 0, 0], value2_.values_array) 89 | value_ = ::Versionomy.parse('1.2 beta 3.4') 90 | value2_ = value_.convert(:rubygems) 91 | assert_equal([1, 2, 'beta', 3, 4, 0, 0, 0], value2_.values_array) 92 | end 93 | 94 | 95 | # Test conversion from standard to rubygems with a "v" prefix 96 | 97 | def test_standard_to_rubygems_with_v 98 | value_ = ::Versionomy.parse('v1.2b3') 99 | value2_ = value_.convert(:rubygems) 100 | assert_equal([1, 2, 'b', 3, 0, 0, 0, 0], value2_.values_array) 101 | value_ = ::Versionomy.parse('V 1.2b3') 102 | value2_ = value_.convert(:rubygems) 103 | assert_equal([1, 2, 'b', 3, 0, 0, 0, 0], value2_.values_array) 104 | end 105 | 106 | 107 | # Test simple conversion from rubygems to standard. 108 | 109 | def test_rubygems_to_standard_simple 110 | value_ = ::Versionomy.parse('1.2', :rubygems) 111 | value2_ = value_.convert(:standard) 112 | assert_equal(@standard_format, value2_.format) 113 | assert_equal([1, 2, 0, 0, :final, 0, 0], value2_.values_array) 114 | value_ = ::Versionomy.parse('1.2.4.1', :rubygems) 115 | value2_ = value_.convert(:standard) 116 | assert_equal(@standard_format, value2_.format) 117 | assert_equal([1, 2, 4, 1, :final, 0, 0], value2_.values_array) 118 | end 119 | 120 | 121 | # Test conversion from rubygems to standard with a beta version 122 | 123 | def test_rubygems_to_standard_beta 124 | value_ = ::Versionomy.parse('1.2.b.3', :rubygems) 125 | value2_ = value_.convert(:standard) 126 | assert_equal([1, 2, 0, 0, :beta, 3, 0], value2_.values_array) 127 | value_ = ::Versionomy.parse('1.2.b3', :rubygems) 128 | value2_ = value_.convert(:standard) 129 | assert_equal([1, 2, 0, 0, :beta, 3, 0], value2_.values_array) 130 | value_ = ::Versionomy.parse('1.2.beta3', :rubygems) 131 | value2_ = value_.convert(:standard) 132 | assert_equal([1, 2, 0, 0, :beta, 3, 0], value2_.values_array) 133 | value_ = ::Versionomy.parse('1.2.b', :rubygems) 134 | value2_ = value_.convert(:standard) 135 | assert_equal([1, 2, 0, 0, :beta, 0, 0], value2_.values_array) 136 | end 137 | 138 | 139 | # Test conversion from rubygems to standard with an expectation of failure 140 | 141 | def test_rubygems_to_standard_fail 142 | value_ = ::Versionomy.parse('1.2.b.3.4.5', :rubygems) 143 | assert_raises(::Versionomy::Errors::ConversionError) do 144 | value_.convert(:standard) 145 | end 146 | value_ = ::Versionomy.parse('1.2.c.3', :rubygems) 147 | assert_raises(::Versionomy::Errors::ConversionError) do 148 | value_.convert(:standard) 149 | end 150 | end 151 | 152 | 153 | # Test equality comparisons between rubygems and standard 154 | 155 | def test_rubygems_to_standard_equality_comparison 156 | assert_operator(::Versionomy.parse('1.2.0', :rubygems), :==, ::Versionomy.parse('1.2')) 157 | assert_operator(::Versionomy.parse('1.2.b.3', :rubygems), :==, ::Versionomy.parse('1.2b3')) 158 | end 159 | 160 | 161 | # Test inequality comparisons between rubygems and standard 162 | 163 | def test_rubygems_to_standard_inequality_comparison 164 | assert_operator(::Versionomy.parse('1.2.3', :rubygems), :<, ::Versionomy.parse('1.2.4')) 165 | assert_operator(::Versionomy.parse('1.2.b.3', :rubygems), :>, ::Versionomy.parse('1.2b2')) 166 | assert_operator(::Versionomy.parse('1.2', :rubygems), :>, ::Versionomy.parse('1.2b1')) 167 | end 168 | 169 | 170 | # Test equality comparisons between standard and rubygems 171 | 172 | def test_standard_to_rubygems_equality_comparison 173 | assert_operator(::Versionomy.parse('1.2.0'), :==, ::Versionomy.parse('1.2', :rubygems)) 174 | assert_operator(::Versionomy.parse('1.2b3'), :==, ::Versionomy.parse('1.2.beta.3', :rubygems)) 175 | end 176 | 177 | 178 | # Test inequality comparisons between standard and rubygems 179 | 180 | def test_standard_to_rubygems_inequality_comparison 181 | assert_operator(::Versionomy.parse('1.2.4'), :>, ::Versionomy.parse('1.2.3', :rubygems)) 182 | assert_operator(::Versionomy.parse('1.2b2'), :<, ::Versionomy.parse('1.2.beta.3', :rubygems)) 183 | assert_operator(::Versionomy.parse('1.2b2'), :<, ::Versionomy.parse('1.2', :rubygems)) 184 | end 185 | 186 | 187 | end 188 | 189 | end 190 | end 191 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Generic Gem Rakefile 4 | # 5 | # ----------------------------------------------------------------------------- 6 | # Copyright 2010 Daniel Azuma 7 | # 8 | # All rights reserved. 9 | # 10 | # Redistribution and use in source and binary forms, with or without 11 | # modification, are permitted provided that the following conditions are met: 12 | # 13 | # * Redistributions of source code must retain the above copyright notice, 14 | # this list of conditions and the following disclaimer. 15 | # * Redistributions in binary form must reproduce the above copyright notice, 16 | # this list of conditions and the following disclaimer in the documentation 17 | # and/or other materials provided with the distribution. 18 | # * Neither the name of the copyright holder, nor the names of any other 19 | # contributors to this software, may be used to endorse or promote products 20 | # derived from this software without specific prior written permission. 21 | # 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 26 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | # POSSIBILITY OF SUCH DAMAGE. 33 | # ----------------------------------------------------------------------------- 34 | ; 35 | 36 | 37 | # Load config if present 38 | 39 | config_path_ = ::File.expand_path('rakefile_config.rb', ::File.dirname(__FILE__)) 40 | load(config_path_) if ::File.exists?(config_path_) 41 | RAKEFILE_CONFIG = {} unless defined?(::RAKEFILE_CONFIG) 42 | 43 | 44 | # Gemspec 45 | 46 | require 'rubygems' 47 | require 'rubygems/package' 48 | gemspec_ = eval(::File.read(::Dir.glob('*.gemspec').first)) 49 | release_gemspec_ = eval(::File.read(::Dir.glob('*.gemspec').first)) 50 | release_gemspec_.version = gemspec_.version.to_s.sub(/\.nonrelease$/, '') 51 | 52 | 53 | # Platform info 54 | 55 | dlext_ = ::RbConfig::CONFIG['DLEXT'] 56 | 57 | platform_ = 58 | case ::RUBY_DESCRIPTION 59 | when /^jruby\s/ then :jruby 60 | when /^ruby\s/ then :mri 61 | when /^rubinius\s/ then :rubinius 62 | else :unknown 63 | end 64 | 65 | platform_suffix_ = 66 | case platform_ 67 | when :mri 68 | if ::RUBY_VERSION =~ /^1\.8\..*$/ 69 | 'mri18' 70 | elsif ::RUBY_VERSION =~ /^1\.9\..*$/ 71 | 'mri19' 72 | elsif ::RUBY_VERSION =~ /^2\..*$/ 73 | 'mri2' 74 | else 75 | raise "Unknown version of Matz Ruby Interpreter (#{::RUBY_VERSION})" 76 | end 77 | when :rubinius then 'rbx' 78 | when :jruby then 'jruby' 79 | else 'unknown' 80 | end 81 | 82 | 83 | # Directories 84 | 85 | doc_directory_ = ::RAKEFILE_CONFIG[:doc_directory] || 'doc' 86 | pkg_directory_ = ::RAKEFILE_CONFIG[:pkg_directory] || 'pkg' 87 | tmp_directory_ = ::RAKEFILE_CONFIG[:tmp_directory] || 'tmp' 88 | 89 | 90 | # Build tasks 91 | 92 | internal_ext_info_ = gemspec_.extensions.map do |extconf_path_| 93 | source_dir_ = ::File.dirname(extconf_path_) 94 | name_ = ::File.basename(source_dir_) 95 | { 96 | :name => name_, 97 | :source_dir => source_dir_, 98 | :extconf_path => extconf_path_, 99 | :source_glob => "#{source_dir_}/*.{c,h}", 100 | :obj_glob => "#{source_dir_}/*.{o,dSYM}", 101 | :suffix_makefile_path => "#{source_dir_}/Makefile_#{platform_suffix_}", 102 | :built_lib_path => "#{source_dir_}/#{name_}.#{dlext_}", 103 | :staged_lib_path => "#{source_dir_}/#{name_}_#{platform_suffix_}.#{dlext_}", 104 | } 105 | end 106 | internal_ext_info_ = [] if platform_ == :jruby 107 | 108 | internal_ext_info_.each do |info_| 109 | file info_[:staged_lib_path] => [info_[:suffix_makefile_path]] + ::Dir.glob(info_[:source_glob]) do 110 | ::Dir.chdir(info_[:source_dir]) do 111 | cp "Makefile_#{platform_suffix_}", 'Makefile' 112 | sh 'make' 113 | rm 'Makefile' 114 | end 115 | mv info_[:built_lib_path], info_[:staged_lib_path] 116 | rm_r ::Dir.glob(info_[:obj_glob]) 117 | end 118 | file info_[:suffix_makefile_path] => info_[:extconf_path] do 119 | ::Dir.chdir(info_[:source_dir]) do 120 | ruby 'extconf.rb' 121 | mv 'Makefile', "Makefile_#{platform_suffix_}" 122 | end 123 | end 124 | end 125 | 126 | task :build_ext => internal_ext_info_.map{ |info_| info_[:staged_lib_path] } do 127 | internal_ext_info_.each do |info_| 128 | target_prefix_ = target_name_ = nil 129 | ::Dir.chdir(info_[:source_dir]) do 130 | ruby 'extconf.rb' 131 | ::File.open('Makefile') do |file_| 132 | file_.each do |line_| 133 | if line_ =~ /^target_prefix\s*=\s*(\S+)\s/ 134 | target_prefix_ = $1 135 | elsif line_ =~ /^TARGET\s*=\s*(\S+)\s/ 136 | target_name_ = $1 137 | end 138 | end 139 | end 140 | rm 'Makefile' 141 | end 142 | raise "Could not find target_prefix in makefile for #{info_[:name]}" unless target_prefix_ 143 | raise "Could not find TARGET in makefile for #{info_[:name]}" unless target_name_ 144 | cp info_[:staged_lib_path], "lib#{target_prefix_}/#{target_name_}.#{dlext_}" 145 | end 146 | end 147 | 148 | 149 | # Clean task 150 | 151 | clean_files_ = [doc_directory_, pkg_directory_, tmp_directory_] + 152 | ::Dir.glob('ext/**/Makefile*') + 153 | ::Dir.glob('ext/**/*.{o,class,log,dSYM}') + 154 | ::Dir.glob("**/*.{bundle,so,dll,rbc,jar}") + 155 | ::Dir.glob('**/.rbx') + 156 | (::RAKEFILE_CONFIG[:extra_clean_files] || []) 157 | task :clean do 158 | clean_files_.each{ |path_| rm_rf path_ } 159 | end 160 | 161 | 162 | # RDoc tasks 163 | 164 | task :build_rdoc => "#{doc_directory_}/index.html" 165 | all_rdoc_files_ = ::Dir.glob("lib/**/*.rb") + gemspec_.extra_rdoc_files 166 | main_rdoc_file_ = ::RAKEFILE_CONFIG[:main_rdoc_file] 167 | main_rdoc_file_ = 'README.rdoc' if !main_rdoc_file_ && ::File.readable?('README.rdoc') 168 | main_rdoc_file_ = ::Dir.glob("*.rdoc").first unless main_rdoc_file_ 169 | file "#{doc_directory_}/index.html" => all_rdoc_files_ do 170 | rm_r doc_directory_ rescue nil 171 | args_ = [] 172 | args_ << '-o' << doc_directory_ 173 | args_ << '--main' << main_rdoc_file_ if main_rdoc_file_ 174 | args_ << '--title' << "#{::RAKEFILE_CONFIG[:product_visible_name] || gemspec_.name.capitalize} #{release_gemspec_.version} Documentation" 175 | args_ << '-f' << 'darkfish' 176 | args_ << '--verbose' if ::ENV['VERBOSE'] 177 | gem 'rdoc' 178 | require 'rdoc/rdoc' 179 | ::RDoc::RDoc.new.document(args_ + all_rdoc_files_) 180 | end 181 | 182 | 183 | # Gem release tasks 184 | 185 | task :build_other 186 | 187 | task :build_gem => :build_other do 188 | if defined?(::Gem::Builder) 189 | ::Gem::Builder.new(gemspec_).build 190 | else 191 | ::Gem::Package.build(gemspec_) 192 | end 193 | mkdir_p(pkg_directory_) 194 | mv "#{gemspec_.name}-#{gemspec_.version}.gem", "#{pkg_directory_}/" 195 | end 196 | 197 | task :build_release => :build_other do 198 | if defined?(::Gem::Builder) 199 | ::Gem::Builder.new(release_gemspec_).build 200 | else 201 | ::Gem::Package.build(release_gemspec_) 202 | end 203 | mkdir_p(pkg_directory_) 204 | mv "#{release_gemspec_.name}-#{release_gemspec_.version}.gem", "#{pkg_directory_}/" 205 | end 206 | 207 | task :release_gem => :build_release do 208 | ::Dir.chdir(pkg_directory_) do 209 | sh "#{::RbConfig::TOPDIR}/bin/gem push #{release_gemspec_.name}-#{release_gemspec_.version}.gem" 210 | end 211 | end 212 | 213 | 214 | # Unit test task 215 | 216 | task :test => [:build_ext, :build_other] do 217 | $:.unshift(::File.expand_path('lib', ::File.dirname(__FILE__))) 218 | if (cases_ = ::ENV['TESTCASE']) 219 | test_files_ = cases_.split(',').map{ |c_| ::Dir.glob("test/#{c_}.rb") }.flatten 220 | else 221 | test_files_ = ::Dir.glob("test/**/tc_*.rb") 222 | end 223 | $VERBOSE = true 224 | test_files_.each do |path_| 225 | load path_ 226 | puts "Loaded testcase #{path_}" 227 | end 228 | end 229 | 230 | 231 | # Default task 232 | 233 | task :default => [:clean, :test] 234 | -------------------------------------------------------------------------------- /lib/versionomy/interface.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy convenience interface 4 | # 5 | # ----------------------------------------------------------------------------- 6 | # Copyright 2008 Daniel Azuma 7 | # 8 | # All rights reserved. 9 | # 10 | # Redistribution and use in source and binary forms, with or without 11 | # modification, are permitted provided that the following conditions are met: 12 | # 13 | # * Redistributions of source code must retain the above copyright notice, 14 | # this list of conditions and the following disclaimer. 15 | # * Redistributions in binary form must reproduce the above copyright notice, 16 | # this list of conditions and the following disclaimer in the documentation 17 | # and/or other materials provided with the distribution. 18 | # * Neither the name of the copyright holder, nor the names of any other 19 | # contributors to this software, may be used to endorse or promote products 20 | # derived from this software without specific prior written permission. 21 | # 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 26 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | # POSSIBILITY OF SUCH DAMAGE. 33 | # ----------------------------------------------------------------------------- 34 | ; 35 | 36 | 37 | # == Versionomy 38 | # 39 | # The Versionomy module contains some convenience methods for creating and 40 | # parsing version numbers. 41 | 42 | module Versionomy 43 | 44 | @default_format = nil 45 | 46 | 47 | class << self 48 | 49 | 50 | # Gets the current default format. Usually this is the "standard" 51 | # format returned by Versionomy::Format.standard. 52 | 53 | def default_format 54 | @default_format ||= Format.standard 55 | end 56 | 57 | 58 | # Sets the default format used by other methods of this convenience 59 | # interface. Usually, this is set to the "standard" format returned by 60 | # Versionomy::Format.standard and should not be changed. 61 | # 62 | # The format can be specified as a format object or the name of a format 63 | # registered with Versionomy::Format. If the format is set to nil, the 64 | # default_format will be reset to the "standard" format. 65 | # 66 | # Raises Versionomy::Errors::UnknownFormatError if a name is given that 67 | # is not registered. 68 | 69 | def default_format=(format_) 70 | if format_.kind_of?(::String) || format_.kind_of?(::Symbol) 71 | format_ = Format.get(format_, true) 72 | end 73 | @default_format = format_ 74 | end 75 | 76 | 77 | # Create a new version number given a hash or array of values, and an 78 | # optional format. 79 | # 80 | # The values should either be a hash of field names and values, or an 81 | # array of values that will be interpreted in field order. 82 | # 83 | # The format can be specified as a format object or the name of a format 84 | # registered with Versionomy::Format. If the format is omitted or set 85 | # to nil, the default_format will be used. 86 | # 87 | # You can also optionally provide default parameters to be used when 88 | # unparsing this value or any derived from it. 89 | # 90 | # Raises Versionomy::Errors::UnknownFormatError if a name is given that 91 | # is not registered. 92 | 93 | def create(values_=nil, format_=nil, unparse_params_=nil) 94 | if format_.kind_of?(::Hash) && unparse_params_.nil? 95 | unparse_params_ = format_ 96 | format_ = nil 97 | end 98 | if format_.kind_of?(::String) || format_.kind_of?(::Symbol) 99 | format_ = Format.get(format_, true) 100 | end 101 | format_ ||= default_format 102 | Value.new(values_ || [], format_, unparse_params_) 103 | end 104 | 105 | 106 | # Create a new version number given a string to parse, and an optional 107 | # format. 108 | # 109 | # The format can be specified as a format object or the name of a format 110 | # registered with Versionomy::Format. If the format is omitted or set 111 | # to nil, the default_format will be used. 112 | # 113 | # The parameter hash, if present, will be passed as parsing parameters 114 | # to the format. 115 | # 116 | # Raises Versionomy::Errors::UnknownFormatError if a name is given that 117 | # is not registered. 118 | # 119 | # May raise Versionomy::Errors::ParseError if parsing failed. 120 | 121 | def parse(str_, format_=nil, parse_params_=nil) 122 | if format_.kind_of?(::Hash) && parse_params_.nil? 123 | parse_params_ = format_ 124 | format_ = nil 125 | end 126 | if format_.kind_of?(::String) || format_.kind_of?(::Symbol) 127 | format_ = Format.get(format_, true) 128 | end 129 | format_ ||= default_format 130 | format_.parse(str_, parse_params_) 131 | end 132 | 133 | 134 | # Convenience method for creating a version number using the Semantic 135 | # Versioning format (see http://semver.org/). 136 | # 137 | # You may pass a string to parse, or a hash with the following keys, all 138 | # of which are optional: 139 | # :major:: 140 | # Major version number 141 | # :minor:: 142 | # Minor version number 143 | # :patch:: 144 | # Patch version number 145 | # :prerelease_suffix:: 146 | # A prerelease suffix (e.g. "b2") 147 | # 148 | # May raise Versionomy::Errors::ParseError if parsing failed. 149 | 150 | def semver(input_) 151 | if input_.kind_of?(::Hash) 152 | create(input_, :semver) 153 | else 154 | parse(input_.to_s, :semver) 155 | end 156 | end 157 | 158 | 159 | # Get the version of the given module as a Versionomy::Value. 160 | # Tries a number of common approaches to embedding version numbers into 161 | # modules, such as string or array constants, submodules containing 162 | # constants, or module method calls. 163 | # Returns the version number, or nil if it wasn't found or couldn't 164 | # be interpreted. 165 | 166 | def version_of(mod_) 167 | version_ = nil 168 | [:VERSION, :VERSION_STRING, :GemVersion].each do |sym_| 169 | if mod_.const_defined?(sym_) 170 | version_ = mod_.const_get(sym_) 171 | break 172 | end 173 | end 174 | if version_.kind_of?(::Module) 175 | if version_.const_defined?(:STRING) 176 | version_ = version_.const_get(:STRING) 177 | elsif version_.const_defined?(:VERSION) 178 | version_ = version_.const_get(:VERSION) 179 | elsif version_.const_defined?(:MAJOR) && version_.const_defined?(:MINOR) && version_.const_defined?(:TINY) 180 | version_ = Value.new([version_.const_get(:MAJOR), version_.const_get(:MINOR), version_.const_get(:TINY)], :standard) 181 | end 182 | end 183 | unless version_.kind_of?(::String) || version_.kind_of?(::Array) || version_.kind_of?(Value) 184 | [:version, :release].each do |sym_| 185 | if mod_.respond_to?(sym_) 186 | version_ = mod_.send(sym_) 187 | break 188 | end 189 | end 190 | end 191 | if version_.kind_of?(::String) 192 | version_ = parse(version_, :standard) rescue nil 193 | elsif version_.kind_of?(::Array) 194 | version_ = create(version_, :standard) rescue nil 195 | elsif !version_.kind_of?(Value) 196 | version_ = nil 197 | end 198 | version_ 199 | end 200 | 201 | 202 | # Get the ruby version as a Versionomy::Value, using the builtin 203 | # constants RUBY_VERSION and RUBY_PATCHLEVEL. 204 | 205 | def ruby_version 206 | @ruby_version ||= begin 207 | version_ = parse(::RUBY_VERSION, :standard) 208 | if version_.release_type == :final 209 | version_ = version_.change({:patchlevel => ::RUBY_PATCHLEVEL}, 210 | :patchlevel_required => true, :patchlevel_delim => '-p') 211 | end 212 | version_ 213 | end 214 | end 215 | 216 | 217 | end 218 | 219 | end 220 | -------------------------------------------------------------------------------- /test/tc_rubygems_basic.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy basic tests on rubygems schema 4 | # 5 | # This file contains tests for the basic use cases on the rubygems schema 6 | # 7 | # ----------------------------------------------------------------------------- 8 | # Copyright 2008 Daniel Azuma 9 | # 10 | # All rights reserved. 11 | # 12 | # Redistribution and use in source and binary forms, with or without 13 | # modification, are permitted provided that the following conditions are met: 14 | # 15 | # * Redistributions of source code must retain the above copyright notice, 16 | # this list of conditions and the following disclaimer. 17 | # * Redistributions in binary form must reproduce the above copyright notice, 18 | # this list of conditions and the following disclaimer in the documentation 19 | # and/or other materials provided with the distribution. 20 | # * Neither the name of the copyright holder, nor the names of any other 21 | # contributors to this software, may be used to endorse or promote products 22 | # derived from this software without specific prior written permission. 23 | # 24 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 28 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 | # POSSIBILITY OF SUCH DAMAGE. 35 | # ----------------------------------------------------------------------------- 36 | 37 | 38 | require 'minitest/autorun' 39 | require 'versionomy' 40 | require 'yaml' 41 | 42 | module Versionomy 43 | module Tests # :nodoc: 44 | 45 | class TestRubygemsBasic < ::Minitest::Test # :nodoc: 46 | 47 | 48 | # Test the default version value. 49 | 50 | def test_default_value 51 | value_ = ::Versionomy.create(nil, :rubygems) 52 | assert_equal(1, value_.field0) 53 | assert_equal(0, value_.field1) 54 | assert_equal(0, value_.field2) 55 | assert_equal(0, value_.field3) 56 | assert_equal(0, value_.field4) 57 | assert_equal(0, value_.field5) 58 | assert_equal(0, value_.field6) 59 | assert_equal(0, value_.field7) 60 | end 61 | 62 | 63 | # Test an arbitrary value. 64 | 65 | def test_arbitrary_value 66 | value_ = ::Versionomy.create([1, 9, 2, 'pre', 2], :rubygems) 67 | assert_equal(1, value_.field0) 68 | assert_equal(9, value_.field1) 69 | assert_equal(2, value_.field2) 70 | assert_equal('pre', value_.field3) 71 | assert_equal(2, value_.field4) 72 | assert_equal(0, value_.field5) 73 | assert_equal(0, value_.field6) 74 | assert_equal(0, value_.field7) 75 | end 76 | 77 | 78 | # Test aliases 79 | 80 | def test_alias_fields 81 | value_ = ::Versionomy.create([1, 9, 2, 'a', 2], :rubygems) 82 | assert_equal(1, value_.major) 83 | assert_equal(9, value_.minor) 84 | end 85 | 86 | 87 | # Test construction using aliases 88 | 89 | def test_alias_field_construction 90 | value_ = ::Versionomy.create({:major => 1, :minor => 9, :field2 => 2}, :rubygems) 91 | assert_equal([1, 9, 2, 0, 0, 0, 0, 0], value_.values_array) 92 | end 93 | 94 | 95 | # Test comparison of numeric values. 96 | 97 | def test_numeric_comparison 98 | value1_ = ::Versionomy.create([1, 9, 2], :rubygems) 99 | value2_ = ::Versionomy.create([1, 9], :rubygems) 100 | assert(value2_ < value1_) 101 | value1_ = ::Versionomy.create([1, 9, 0], :rubygems) 102 | value2_ = ::Versionomy.create([1, 9], :rubygems) 103 | assert(value2_ == value1_) 104 | end 105 | 106 | 107 | # Test comparison of string values. 108 | 109 | def test_string_comparison 110 | value1_ = ::Versionomy.create([1, 9, 2, 'a', 2], :rubygems) 111 | value2_ = ::Versionomy.create([1, 9, 2, 'b', 1], :rubygems) 112 | assert(value2_ > value1_) 113 | end 114 | 115 | 116 | # Test comparison of numeric and string values. 117 | 118 | def test_numeric_and_string_comparison 119 | value1_ = ::Versionomy.create([1, 9, 2, 'a', 2], :rubygems) 120 | value2_ = ::Versionomy.create([1, 9, 2, 1], :rubygems) 121 | assert(value2_ > value1_) 122 | end 123 | 124 | 125 | # Test parsing numeric. 126 | 127 | def test_parsing_numeric 128 | value_ = ::Versionomy.parse('2.0.1.1.4.6', :rubygems) 129 | assert_equal([2, 0, 1, 1, 4, 6, 0, 0], value_.values_array) 130 | assert_equal('2.0.1.1.4.6', value_.unparse) 131 | end 132 | 133 | 134 | # Test parsing with a string. 135 | 136 | def test_parsing_with_string 137 | value_ = ::Versionomy.parse('1.9.2.pre.2', :rubygems) 138 | assert_equal([1, 9, 2, 'pre', 2, 0, 0, 0], value_.values_array) 139 | assert_equal('1.9.2.pre.2', value_.unparse) 140 | end 141 | 142 | 143 | # Test parsing with trailing zeros. 144 | 145 | def test_parsing_trailing_zeros 146 | value_ = ::Versionomy.parse('2.0.0', :rubygems) 147 | assert_equal([2, 0, 0, 0, 0, 0, 0, 0], value_.values_array) 148 | assert_equal('2.0.0', value_.unparse) 149 | assert_equal('2.0.0.0.0', value_.unparse(:required_fields => [:field4])) 150 | assert_equal('2.0', value_.unparse(:optional_fields => [:field2])) 151 | end 152 | 153 | 154 | # Test bumping a numeric field. 155 | 156 | def test_bump_numeric 157 | value_ = ::Versionomy.create([1, 9, 2, 'a', 2], :rubygems) 158 | value_ = value_.bump(:field2) 159 | assert_equal([1, 9, 3, 0, 0, 0, 0, 0], value_.values_array) 160 | end 161 | 162 | 163 | # Test bumping a string field. 164 | 165 | def test_bump_string 166 | value_ = ::Versionomy.create([1, 9, 2, 'a', 2], :rubygems) 167 | value_ = value_.bump(:field3) 168 | assert_equal([1, 9, 2, 'b', 0, 0, 0, 0], value_.values_array) 169 | end 170 | 171 | 172 | # Test bumping an alias field. 173 | 174 | def test_bump_alias 175 | value_ = ::Versionomy.create([1, 9, 2, 'a', 2], :rubygems) 176 | value_ = value_.bump(:minor) 177 | assert_equal([1, 10, 0, 0, 0, 0, 0, 0], value_.values_array) 178 | end 179 | 180 | 181 | # Test changing an alias field. 182 | 183 | def test_change_alias 184 | value_ = ::Versionomy.create([1, 8, 7, 'a', 2], :rubygems) 185 | value_ = value_.change(:minor => 9) 186 | assert_equal([1, 9, 7, 'a', 2, 0, 0, 0], value_.values_array) 187 | end 188 | 189 | 190 | # Test "prerelase?" custom method 191 | 192 | def test_method_prereleasep 193 | value_ = ::Versionomy.create([1, 9, 2, 'a', 2], :rubygems) 194 | assert_equal(true, value_.prerelease?) 195 | value_ = ::Versionomy.create([1, 9, 2, 2], :rubygems) 196 | assert_equal(false, value_.prerelease?) 197 | end 198 | 199 | 200 | # Test "relase" custom method 201 | 202 | def test_method_release 203 | value_ = ::Versionomy.create([1, 9, 2, 'a', 2], :rubygems) 204 | value2_ = value_.release 205 | assert_equal([1, 9, 2, 0, 0, 0, 0, 0], value2_.values_array) 206 | value_ = ::Versionomy.create([1, 9, 2, 5, 2], :rubygems) 207 | value2_ = value_.release 208 | assert_equal(value_, value2_) 209 | end 210 | 211 | 212 | # Test "parts" custom method 213 | 214 | def test_method_parts 215 | value_ = ::Versionomy.create([1, 9, 2, 'a', 2], :rubygems) 216 | assert_equal([1, 9, 2, 'a', 2], value_.parts) 217 | value_ = ::Versionomy.create([1, 9, 0], :rubygems) 218 | assert_equal([1, 9], value_.parts) 219 | end 220 | 221 | 222 | # Test marshalling 223 | 224 | def test_marshal 225 | value_ = ::Versionomy.create([1, 9, 2, 'a', 2], :rubygems) 226 | str_ = ::Marshal.dump(value_) 227 | value2_ = ::Marshal.load(str_) 228 | assert_equal(value_, value2_) 229 | end 230 | 231 | 232 | # Test YAML 233 | 234 | def test_yaml 235 | value_ = ::Versionomy.create([1, 9, 2, 'a', 2], :rubygems) 236 | str_ = ::YAML.dump(value_) 237 | value2_ = ::YAML.load(str_) 238 | assert_equal(value_, value2_) 239 | end 240 | 241 | 242 | end 243 | 244 | end 245 | end 246 | -------------------------------------------------------------------------------- /lib/versionomy/format.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy format namespace 4 | # 5 | # ----------------------------------------------------------------------------- 6 | # Copyright 2008 Daniel Azuma 7 | # 8 | # All rights reserved. 9 | # 10 | # Redistribution and use in source and binary forms, with or without 11 | # modification, are permitted provided that the following conditions are met: 12 | # 13 | # * Redistributions of source code must retain the above copyright notice, 14 | # this list of conditions and the following disclaimer. 15 | # * Redistributions in binary form must reproduce the above copyright notice, 16 | # this list of conditions and the following disclaimer in the documentation 17 | # and/or other materials provided with the distribution. 18 | # * Neither the name of the copyright holder, nor the names of any other 19 | # contributors to this software, may be used to endorse or promote products 20 | # derived from this software without specific prior written permission. 21 | # 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 26 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | # POSSIBILITY OF SUCH DAMAGE. 33 | # ----------------------------------------------------------------------------- 34 | ; 35 | 36 | 37 | require 'thread' 38 | require 'monitor' 39 | 40 | 41 | module Versionomy 42 | 43 | 44 | # === Version number format. 45 | # 46 | # A format controls the parsing and unparsing of a version number. 47 | # Any time a version number is parsed from a string, a format is provided 48 | # to parse it. Similarly, every version number value references a format 49 | # that is used to unparse it back into a string. 50 | # 51 | # A format is always tied to a particular schema and knows how to parse 52 | # only that schema's version numbers. 53 | # 54 | # Under many circumstances, you should use the standard format, which 55 | # can be retrieved by calling Versionomy::Format#standard. This format 56 | # understands most common version numbers, including prerelease 57 | # (e.g. alpha, beta, release candidate, etc.) forms and patchlevels. 58 | # 59 | # You may also create your own formats, either by implementing the 60 | # format contract (see Versionomy::Format::Base), or by using the 61 | # Versionomy::Format::Delimiter tool, which can be used to construct 62 | # parsers for many version number formats. 63 | # 64 | # === Format registry 65 | # 66 | # Formats may be registered with Versionomy and given a name using the 67 | # methods of this module. This allows version numbers to be serialized 68 | # with their format. When a version number is serialized, its format 69 | # name is written to the stream, along with the version number's string 70 | # representation. When the version number is reconstructed, its format 71 | # is looked up by name so versionomy can determine how to parse the 72 | # string. 73 | # 74 | # Format names are strings that may include letters, numerals, dashes, 75 | # underscores, and periods. By convention, periods are used as namespace 76 | # delimiters. Format names without a namespace (that is, with no periods) 77 | # are considered reserved for standard versionomy formats. If you define 78 | # your own format, you should use a name that includes a namespace (e.g. 79 | # "mycompany.LibraryVersion") to reduce the chance of name collisions. 80 | # 81 | # You may register formats directly using the register method, or set it 82 | # up to be autoloaded on demand. When a format is requested, if it has 83 | # not been registered explicitly, Versionomy looks for a format definition 84 | # file for that format. Such a file has the name of the format, with the 85 | # ".rb" extension for ruby (e.g. "mycompany.LibraryVersion.rb") and must 86 | # be located in a directory in versionomy's format lookup path. By 87 | # default, the directory containing versionomy's predefined formats 88 | # (such as "standard") is in this path. However, you may add your own 89 | # directories using the add_directory method. This lets you autoload your 90 | # own formats. A format definition file itself must contain ruby code 91 | # that defines the format and registers it using the correct name. See 92 | # the files in the "lib/versionomy/format_definitions/" directory for 93 | # examples. 94 | 95 | module Format 96 | 97 | @mutex = ::Mutex.new 98 | @load_mutex = ::Monitor.new 99 | @directories = [::File.expand_path(::File.dirname(__FILE__)+'/format_definitions')] 100 | @names_to_formats = {} 101 | @formats_to_names = {} 102 | 103 | class << self 104 | 105 | 106 | # Add a directory to the format path. 107 | # 108 | # The format path is an array of directory paths. These directories 109 | # are searched for format definitions if a format name that has not 110 | # been registered is requested. 111 | # 112 | # If high_priority_ is set to true, the directory is added to the 113 | # front of the lookup path; otherwise it is added to the back. 114 | 115 | def add_directory(path_, high_priority_=false) 116 | path_ = ::File.expand_path(path_) 117 | @mutex.synchronize do 118 | unless @directories.include?(path_) 119 | if high_priority_ 120 | @directories.unshift(path_) 121 | else 122 | @directories.push(path_) 123 | end 124 | end 125 | end 126 | end 127 | 128 | 129 | # Get the format with the given name. 130 | # 131 | # If the given name has not been defined, attempts to autoload it from 132 | # a format definition file. See the description of the Format module 133 | # for details on this procedure. 134 | # 135 | # If the given name still cannot be resolved, and strict is set to 136 | # true, raises Versionomy::Errors::UnknownFormatError. If strict is 137 | # set to false, returns nil if the given name cannot be resolved. 138 | 139 | def get(name_, strict_=false) 140 | name_ = _check_name(name_) 141 | format_ = @mutex.synchronize{ @names_to_formats[name_] } 142 | if format_.nil? 143 | # Attempt to find the format in the directory path 144 | dirs_ = @mutex.synchronize{ @directories.dup } 145 | dirs_.each do |dir_| 146 | path_ = "#{dir_}/#{name_}.rb" 147 | if ::File.readable?(path_) 148 | @load_mutex.synchronize{ ::Kernel.load(path_) } 149 | end 150 | format_ = @mutex.synchronize{ @names_to_formats[name_] } 151 | break unless format_.nil? 152 | end 153 | end 154 | if format_.nil? && strict_ 155 | raise Errors::UnknownFormatError, name_ 156 | end 157 | format_ 158 | end 159 | 160 | 161 | # Determines whether a format with the given name has been registered 162 | # explicitly. Does not attempt to autoload the format. 163 | 164 | def registered?(name_) 165 | name_ = _check_name(name_) 166 | @mutex.synchronize{ @names_to_formats.include?(name_) } 167 | end 168 | 169 | 170 | # Register the given format under the given name. 171 | # 172 | # Valid names may contain only letters, digits, underscores, dashes, 173 | # and periods. 174 | # 175 | # Raises Versionomy::Errors::FormatRedefinedError if the name has 176 | # already been defined. 177 | 178 | def register(name_, format_, silent_=false) 179 | name_ = _check_name(name_) 180 | @mutex.synchronize do 181 | if @names_to_formats.include?(name_) 182 | unless silent_ 183 | raise Errors::FormatRedefinedError, name_ 184 | end 185 | else 186 | @names_to_formats[name_] = format_ 187 | @formats_to_names[format_.object_id] = name_ 188 | end 189 | end 190 | end 191 | 192 | 193 | # Get the canonical name for the given format, as a string. 194 | # This is the first name the format was registered under. 195 | # 196 | # If the given format was never registered, and strict is set to true, 197 | # raises Versionomy::Errors::UnknownFormatError. If strict is set to 198 | # false, returns nil if the given format was never registered. 199 | 200 | def canonical_name_for(format_, strict_=false) 201 | name_ = @mutex.synchronize{ @formats_to_names[format_.object_id] } 202 | if name_.nil? && strict_ 203 | raise Errors::UnknownFormatError 204 | end 205 | name_ 206 | end 207 | 208 | 209 | private 210 | 211 | def _check_name(name_) # :nodoc: 212 | name_ = name_.to_s 213 | unless name_ =~ /\A[\w.-]+\z/ 214 | raise ::ArgumentError, "Illegal name: #{name_.inspect}" 215 | end 216 | name_ 217 | end 218 | 219 | 220 | end 221 | 222 | end 223 | 224 | 225 | # Versionomy::Formats is an alias for Versionomy::Format, for backward 226 | # compatibility with version 0.1.0 code. It is deprecated; use 227 | # Versionomy::Format instead. 228 | Formats = Format 229 | 230 | 231 | end 232 | -------------------------------------------------------------------------------- /lib/versionomy/format_definitions/semver.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy semver format implementation 4 | # 5 | # ----------------------------------------------------------------------------- 6 | # Copyright 2010 Daniel Azuma 7 | # 8 | # All rights reserved. 9 | # 10 | # Redistribution and use in source and binary forms, with or without 11 | # modification, are permitted provided that the following conditions are met: 12 | # 13 | # * Redistributions of source code must retain the above copyright notice, 14 | # this list of conditions and the following disclaimer. 15 | # * Redistributions in binary form must reproduce the above copyright notice, 16 | # this list of conditions and the following disclaimer in the documentation 17 | # and/or other materials provided with the distribution. 18 | # * Neither the name of the copyright holder, nor the names of any other 19 | # contributors to this software, may be used to endorse or promote products 20 | # derived from this software without specific prior written permission. 21 | # 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 26 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | # POSSIBILITY OF SUCH DAMAGE. 33 | # ----------------------------------------------------------------------------- 34 | ; 35 | 36 | 37 | module Versionomy 38 | 39 | module Format 40 | 41 | 42 | # Get the semver format. 43 | # This is identical to calling get('semver'). 44 | # 45 | # The semver format is designed to conform to the Semantic Versioning 46 | # spec by Tom Preston-Warner. See http://semver.org/ 47 | # 48 | # For the exact annotated definition of the semver schema and format, 49 | # see the source code for Versionomy::Format::Semver#create. 50 | 51 | def self.semver 52 | get('semver') 53 | end 54 | 55 | 56 | # This is a namespace for the implementation of the semver schema 57 | # and format. 58 | 59 | module Semver 60 | 61 | 62 | # Extra methods added to version values that use the semver schema. 63 | 64 | module ExtraMethods 65 | 66 | 67 | # Returns true if the version is a prerelease version-- that is, 68 | # if the prerelease_suffix is nonempty. 69 | 70 | def prerelease? 71 | prerelease_suffix.length > 0 72 | end 73 | 74 | 75 | # Returns the release for this version. 76 | # For example, converts "1.2.0a1" to "1.2.0". 77 | # Non-prerelease versions return themselves unchanged. 78 | 79 | def release 80 | prerelease? ? self.change(:prerelease_suffix => '') : self 81 | end 82 | 83 | 84 | # Returns true if this version is compatible with the given version, 85 | # according to the Semantic Versioning specification. 86 | # For example, 1.1.0 is compatible with 1.0.0 but not vice versa, 87 | # 1.1.1 and 1.1.0 are compatible with each other, while 1.0.0 and 88 | # 2.0.0 are mutually incompatible. 89 | 90 | def compatible_with?(version_) 91 | self.major == version_.major ? self.minor >= version_.minor : false 92 | end 93 | 94 | 95 | end 96 | 97 | 98 | # Create the semver format. 99 | # This method is called internally when Versionomy loads the semver 100 | # format, and you should not need to call it again. It is documented 101 | # so that you can inspect its source code from RDoc, since the source 102 | # contains useful examples of how to use the schema and format 103 | # definition DSLs. 104 | 105 | def self.create 106 | 107 | # The following is the definition of the semver schema 108 | schema_ = Schema.create do 109 | 110 | # The first field has the default value of 1. All other fields 111 | # have a default value of 0. Thus, the default version number 112 | # overall is "1.0". 113 | field(:major, :type => :integer, :default_value => 1) do 114 | field(:minor, :type => :integer) do 115 | field(:patch, :type => :integer) do 116 | field(:prerelease_suffix, :type => :string) do 117 | to_compare do |a_, b_| 118 | a_.length == 0 ? (b_.length == 0 ? 0 : 1) : (b_.length == 0 ? -1 : a_ <=> b_) 119 | end 120 | end 121 | end 122 | end 123 | end 124 | 125 | # An alias 126 | alias_field(:special_suffix, :prerelease_suffix) 127 | 128 | # Add the methods in this module to each value 129 | add_module(Format::Semver::ExtraMethods) 130 | end 131 | 132 | # The following is the definition of the standard format. It 133 | # understands the standard schema defined above. 134 | Format::Delimiter.new(schema_) do 135 | 136 | # All version number strings must start with the major version. 137 | # Unlike other fields, it is not preceded by the usual "dot" 138 | # delimiter, but it can be preceded by a "v" indicator. 139 | field(:major) do 140 | recognize_number(:delimiter_regexp => 'v?', :default_delimiter => '') 141 | end 142 | 143 | # The remainder of the core version number are represented as 144 | # integers delimited by periods. These fields are required. 145 | field(:minor) do 146 | recognize_number 147 | end 148 | field(:patch) do 149 | recognize_number 150 | end 151 | 152 | # The optional prerelease field is represented as a string 153 | # beginning with an alphabetic character. 154 | field(:prerelease_suffix) do 155 | recognize_regexp('[a-zA-Z][0-9a-zA-Z-]*', :default_value_optional => true, 156 | :delimiter_regexp => '', :default_delimiter => '') 157 | end 158 | end 159 | end 160 | 161 | 162 | end 163 | 164 | 165 | register('semver', Format::Semver.create, true) 166 | 167 | 168 | end 169 | 170 | 171 | module Conversion 172 | 173 | 174 | # This is a namespace for the implementation of the conversion between 175 | # the semver and standard formats. 176 | 177 | module Semver 178 | 179 | 180 | # Create the conversion from standard to semver format. 181 | # This method is called internally when Versionomy loads the semver 182 | # format, and you should not need to call it again. It is documented 183 | # so that you can inspect its source code from RDoc, since the source 184 | # contains useful examples of how to use the conversion DSLs. 185 | 186 | def self.create_standard_to_semver 187 | 188 | # We'll use a parsing conversion. 189 | Conversion::Parsing.new do 190 | 191 | # Sanity check the original value and make sure it doesn't 192 | # include fields that we don't support. 193 | to_modify_original_value do |value_, convert_params_| 194 | if value_.has_field?(:patchlevel) && value_.patchlevel != 0 195 | raise Errors::ConversionError, 'Cannot convert a version with a patchlevel to semver' 196 | end 197 | if value_.tiny2 != 0 198 | raise Errors::ConversionError, 'Cannot convert a version more than three fields to semver' 199 | end 200 | value_ 201 | end 202 | 203 | # We're going to modify how the standard format version is 204 | # unparsed, so the semver format will have a better chance 205 | # of parsing it. 206 | to_modify_unparse_params do |params_, convert_params_| 207 | 208 | # All three fields are required 209 | params_[:minor_required] = true 210 | params_[:tiny_required] = true 211 | 212 | # If the standard format version has a prerelease notation, 213 | # make sure it isn't set off using a delimiter. 214 | params_[:release_type_delim] = '' 215 | params_[:development_version_delim] = '' 216 | params_[:development_minor_delim] = '-' 217 | params_[:alpha_version_delim] = '' 218 | params_[:alpha_minor_delim] = '-' 219 | params_[:beta_version_delim] = '' 220 | params_[:beta_minor_delim] = '-' 221 | params_[:release_candidate_version_delim] = '' 222 | params_[:release_candidate_minor_delim] = '-' 223 | params_[:preview_version_delim] = '' 224 | params_[:preview_minor_delim] = '-' 225 | 226 | # If the standard format version includes a "v" prefix, strip it 227 | params_[:major_delim] = nil 228 | 229 | params_ 230 | end 231 | 232 | end 233 | 234 | end 235 | 236 | 237 | # Create the conversion from semver to standard format. 238 | # This method is called internally when Versionomy loads the semver 239 | # format, and you should not need to call it again. It is documented 240 | # so that you can inspect its source code from RDoc, since the source 241 | # contains useful examples of how to use the conversion DSLs. 242 | 243 | def self.create_semver_to_standard 244 | 245 | # We'll use a parsing conversion. 246 | Conversion::Parsing.new do 247 | 248 | # Handle the case where the semver version ends with a string 249 | # field, e.g. "1.0b". We want to treat this like "1.0b0" rather 250 | # than "1.0-2" since the semver semantic states that this is a 251 | # prerelease version. So we add 0 to the end of the parsed string 252 | # if it ends in a letter. 253 | to_modify_string do |str_, convert_params_| 254 | str_.gsub(/([[:alpha:]])\z/, '\10') 255 | end 256 | 257 | end 258 | 259 | end 260 | 261 | 262 | end 263 | 264 | 265 | register(:standard, :semver, Conversion::Semver.create_standard_to_semver, true) 266 | register(:semver, :standard, Conversion::Semver.create_semver_to_standard, true) 267 | 268 | 269 | end 270 | 271 | 272 | end 273 | -------------------------------------------------------------------------------- /lib/versionomy/schema/wrapper.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy schema wrapper class 4 | # 5 | # ----------------------------------------------------------------------------- 6 | # Copyright 2008 Daniel Azuma 7 | # 8 | # All rights reserved. 9 | # 10 | # Redistribution and use in source and binary forms, with or without 11 | # modification, are permitted provided that the following conditions are met: 12 | # 13 | # * Redistributions of source code must retain the above copyright notice, 14 | # this list of conditions and the following disclaimer. 15 | # * Redistributions in binary form must reproduce the above copyright notice, 16 | # this list of conditions and the following disclaimer in the documentation 17 | # and/or other materials provided with the distribution. 18 | # * Neither the name of the copyright holder, nor the names of any other 19 | # contributors to this software, may be used to endorse or promote products 20 | # derived from this software without specific prior written permission. 21 | # 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 26 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | # POSSIBILITY OF SUCH DAMAGE. 33 | # ----------------------------------------------------------------------------- 34 | ; 35 | 36 | 37 | module Versionomy 38 | 39 | module Schema 40 | 41 | 42 | # Creates a schema. 43 | # Returns an object of type Versionomy::Schema::Wrapper. 44 | # 45 | # You may either pass a root field, or provide a block to use to build 46 | # fields. If you provide a block, you must use the methods in 47 | # Versionomy::Schema::Builder in the block to create the root field. 48 | 49 | def self.create(field_=nil, opts_={}, &block_) 50 | if field_ && block_ 51 | raise ::ArgumentError, 'You may provide either a root field or block but not both' 52 | end 53 | if block_ 54 | builder_ = Schema::Builder.new 55 | ::Blockenspiel.invoke(block_, builder_) 56 | field_ = builder_._get_field 57 | modules_ = builder_._get_modules 58 | aliases_ = builder_._get_aliases 59 | else 60 | modules_ = opts_[:modules] || [] 61 | end 62 | Schema::Wrapper.new(field_, modules_, aliases_) 63 | end 64 | 65 | 66 | # Schemas are generally referenced through an object of this class. 67 | 68 | class Wrapper 69 | 70 | 71 | # Create a new schema wrapper object given a root field. 72 | # This is a low-level method. Usually you should call 73 | # Versionomy::Schema#create instead. 74 | 75 | def initialize(field_, modules_=[], aliases_={}) 76 | @root_field = field_ 77 | @names = @root_field._descendants_by_name 78 | @modules = modules_ 79 | @aliases = {} 80 | aliases_.each do |k_,v_| 81 | k_ = k_.to_sym 82 | v_ = v_.to_sym 83 | if @names.include?(v_) && !@names.include?(k_) 84 | @aliases[k_] = v_ 85 | end 86 | end 87 | end 88 | 89 | 90 | def inspect # :nodoc: 91 | "#<#{self.class}:0x#{object_id.to_s(16)} root=#{@root_field.inspect}>" 92 | end 93 | 94 | def to_s # :nodoc: 95 | inspect 96 | end 97 | 98 | 99 | # Returns true if this schema is equivalent to the other schema. 100 | # Two schemas are equivalent if their root fields are the same-- 101 | # which means that the entire field tree is the same-- and they 102 | # include the same value modules. 103 | # Note that this is different from the definition of ==. 104 | 105 | def eql?(obj_) 106 | return false unless obj_.kind_of?(Schema::Wrapper) 107 | return @root_field == obj_.root_field && @modules == obj_.modules && @aliases == obj_.aliases 108 | end 109 | 110 | 111 | # Returns true if this schema is compatible with the other schema. 112 | # Two schemas are compatible if their root fields are the same-- 113 | # which means that the entire field tree is the same. They may, 114 | # however, include different value modules. 115 | # Note that this is different from the definition of eql?. 116 | 117 | def ==(obj_) 118 | eql?(obj_) 119 | end 120 | 121 | 122 | # If the RHS is a schema, returns true if the schemas are equivalent. 123 | # If the RHS is a value, returns true if the value uses this schema. 124 | 125 | def ===(obj_) 126 | if obj_.kind_of?(Value) 127 | obj_.schema == self 128 | else 129 | obj_ == self 130 | end 131 | end 132 | 133 | 134 | def hash # :nodoc: 135 | @hash ||= @root_field.hash ^ @modules.hash 136 | end 137 | 138 | 139 | # Returns the root (most significant) field in this schema. 140 | 141 | def root_field 142 | @root_field 143 | end 144 | 145 | 146 | # Return the canonical field name given a name, or nil if the name 147 | # is not recognized. 148 | 149 | def canonical_name(name_) 150 | name_ = name_.to_sym 151 | name_ = @aliases[name_] || name_ 152 | @names.include?(name_) ? name_ : nil 153 | end 154 | 155 | 156 | # Return the field with the given name, or nil if the given name 157 | # is not found in this schema. If include_aliases_ is set to true, 158 | # this also supports lookup by alias. 159 | 160 | def field_named(name_, include_aliases_=false) 161 | name_ = name_.to_sym 162 | name_ = @aliases[name_] || name_ if include_aliases_ 163 | @names[name_] 164 | end 165 | 166 | 167 | # Returns an array of names present in this schema, in no particular 168 | # order. Does not include aliases. 169 | 170 | def names 171 | @names.keys 172 | end 173 | 174 | 175 | # Returns an array of modules that should be included in values that 176 | # use this schema. 177 | 178 | def modules 179 | @modules.dup 180 | end 181 | 182 | 183 | # Returns a hash of field name aliases. 184 | 185 | def aliases 186 | @aliases.dup 187 | end 188 | 189 | 190 | end 191 | 192 | 193 | # These methods are available in a schema definition block given to 194 | # Versionomy::Schema#create. 195 | 196 | class Builder 197 | 198 | include ::Blockenspiel::DSL 199 | 200 | def initialize() # :nodoc: 201 | @field = nil 202 | @modules = [] 203 | @aliases = {} 204 | @defaults = { :integer => {}, :string => {}, :symbol => {} } 205 | end 206 | 207 | 208 | # Create the root field. 209 | # 210 | # Recognized options include: 211 | # 212 | # :type:: 213 | # Type of field. This should be :integer, :string, 214 | # or :symbol. Default is :integer. 215 | # :default_value:: 216 | # Default value for the field if no value is explicitly set. Default 217 | # is 0 for an integer field, the empty string for a string field, or 218 | # the first symbol added for a symbol field. 219 | # 220 | # You may provide an optional block. Within the block, you may call 221 | # methods of Versionomy::Schema::FieldBuilder to customize this field. 222 | # 223 | # Raises Versionomy::Errors::IllegalValueError if the given default 224 | # value is not legal. 225 | # 226 | # Raises Versionomy::Errors::RangeOverlapError if a root field has 227 | # already been created. 228 | 229 | def field(name_, opts_={}, &block_) 230 | if @field 231 | raise Errors::RangeOverlapError, "Root field already defined" 232 | end 233 | @field = Schema::Field.new(name_, opts_.merge(:master_builder => self), &block_) 234 | end 235 | 236 | 237 | # Create a field alias. 238 | 239 | def alias_field(alias_name_, field_name_) 240 | @aliases[alias_name_.to_sym] = field_name_.to_sym 241 | end 242 | 243 | 244 | # Add a module to the schema. All values that use this schema will 245 | # include this module. This provides a way to add schema-specific 246 | # capabilities to version numbers. 247 | 248 | def add_module(mod_) 249 | @modules << mod_ 250 | end 251 | 252 | 253 | # Provide a default bump procedure for the given type. 254 | # The type should be :integer, :string, or 255 | # :symbol. You must provide a block that takes a field value 256 | # and returns the "bumped" value. This procedure will be used for 257 | # all fields of this type, unless explicitly overridden by the field. 258 | 259 | def to_bump_type(type_, &block_) 260 | @defaults[type_][:bump] = block_ 261 | end 262 | 263 | 264 | # Provide a default compare procedure for the given type. 265 | # The type should be :integer, :string, or 266 | # :symbol. You must provide a block that takes two values 267 | # and returns a standard comparison result-- that is, a negative 268 | # integer if the first value is less, 0 if the values are equal, or a 269 | # positive integer if the first value is greater. This procedure will 270 | # be used for all fields of this type, unless explicitly overridden 271 | # by the field. 272 | 273 | def to_compare_type(type_, &block_) 274 | @defaults[type_][:compare] = block_ 275 | end 276 | 277 | 278 | # Provide a default canonicalization procedure for the given type. 279 | # The type should be :integer, :string, or 280 | # :symbol. You must provide a block that takes a field value 281 | # and returns the canonical value. This procedure will be used for 282 | # all fields of this type, unless explicitly overridden by the field. 283 | 284 | def to_canonicalize_type(type_, &block_) 285 | @defaults[type_][:canonicalize] = block_ 286 | end 287 | 288 | 289 | # Provide a default value for the given type. 290 | # The type should be :integer, :string, or 291 | # :symbol. You must provide a default value that will be 292 | # used for all fields of this type, unless explicitly overridden by 293 | # the field. 294 | 295 | def default_value_for_type(type_, value_) 296 | @defaults[type_][:value] = value_ 297 | end 298 | 299 | 300 | def _get_field # :nodoc: 301 | @field 302 | end 303 | 304 | def _get_modules # :nodoc: 305 | @modules 306 | end 307 | 308 | def _get_aliases # :nodoc: 309 | @aliases 310 | end 311 | 312 | def _get_default_setting(type_, setting_) # :nodoc: 313 | @defaults[type_][setting_] 314 | end 315 | 316 | end 317 | 318 | 319 | end 320 | 321 | end 322 | -------------------------------------------------------------------------------- /lib/versionomy/format_definitions/rubygems.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy standard format implementation 4 | # 5 | # ----------------------------------------------------------------------------- 6 | # Copyright 2008 Daniel Azuma 7 | # 8 | # All rights reserved. 9 | # 10 | # Redistribution and use in source and binary forms, with or without 11 | # modification, are permitted provided that the following conditions are met: 12 | # 13 | # * Redistributions of source code must retain the above copyright notice, 14 | # this list of conditions and the following disclaimer. 15 | # * Redistributions in binary form must reproduce the above copyright notice, 16 | # this list of conditions and the following disclaimer in the documentation 17 | # and/or other materials provided with the distribution. 18 | # * Neither the name of the copyright holder, nor the names of any other 19 | # contributors to this software, may be used to endorse or promote products 20 | # derived from this software without specific prior written permission. 21 | # 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 26 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | # POSSIBILITY OF SUCH DAMAGE. 33 | # ----------------------------------------------------------------------------- 34 | ; 35 | 36 | 37 | module Versionomy 38 | 39 | module Format 40 | 41 | 42 | # Get the rubygems format. 43 | # This is identical to calling get('rubygems'). 44 | # 45 | # The rubygems format is designed to be parse-compatible with the 46 | # Gem::Version class used in rubygems. The only caveat is, whereas 47 | # Gem::Version handles an arbitrary number of fields, this format is 48 | # limited to a maximum of 8. 49 | # 50 | # For the exact annotated definition of the rubygems schema and format, 51 | # see the source code for Versionomy::Format::Rubygems#create. 52 | 53 | def self.rubygems 54 | get('rubygems') 55 | end 56 | 57 | 58 | # This is a namespace for the implementation of the Rubygems schema 59 | # and format. 60 | 61 | module Rubygems 62 | 63 | 64 | # Extra methods added to version values that use the rubygems schema. 65 | 66 | module ExtraMethods 67 | 68 | 69 | # Returns true if the version is a prerelease version-- that is, 70 | # if any of the fields is non-numeric. 71 | # 72 | # This behaves the same as the Gem::Version#prerelease? method 73 | # in rubygems. 74 | 75 | def prerelease? 76 | values_array.any?{ |val_| val_.kind_of?(::String) } 77 | end 78 | 79 | 80 | # Returns the release for this version. 81 | # For example, converts "1.2.0.a.1" to "1.2.0". 82 | # Non-prerelease versions return themselves. 83 | # 84 | # This behaves the same as the Gem::Version#release method 85 | # in rubygems. 86 | 87 | def release 88 | values_ = [] 89 | self.each_field_object do |field_, val_| 90 | break unless val_.kind_of?(::Integer) 91 | values_ << val_ 92 | end 93 | Value.new(values_, self.format, self.unparse_params) 94 | end 95 | 96 | 97 | # Returns a list of the field values, in field order, with 98 | # trailing zeroes stripped off. 99 | # 100 | # This behaves the same as the Gem::Version#parts method 101 | # in rubygems. 102 | 103 | def parts 104 | unless defined?(@parts) 105 | @parts = values_array 106 | @parts.pop while @parts.size > 1 && @parts.last == 0 107 | end 108 | @parts 109 | end 110 | 111 | 112 | end 113 | 114 | 115 | # Create the rubygems format. 116 | # This method is called internally when Versionomy loads the rubygems 117 | # format, and you should not need to call it again. It is documented 118 | # so that you can inspect its source code from RDoc, since the source 119 | # contains useful examples of how to use the schema and format 120 | # definition DSLs. 121 | 122 | def self.create 123 | 124 | # The following is the definition of the rubygems schema 125 | schema_ = Schema.create do 126 | 127 | # Global comparison function 128 | to_compare_type(:string) do |a_, b_| 129 | if a_.kind_of?(::Integer) 130 | if b_.kind_of?(::Integer) 131 | a_ <=> b_ 132 | else 133 | 1 134 | end 135 | else 136 | if b_.kind_of?(::Integer) 137 | -1 138 | else 139 | a_ <=> b_ 140 | end 141 | end 142 | end 143 | 144 | # Global canonicalization function 145 | to_canonicalize_type(:string) do |val_| 146 | if val_.kind_of?(::Integer) 147 | val_ 148 | else 149 | val_ = val_.to_s 150 | if val_ =~ /\A\d*\z/ 151 | val_.to_i 152 | else 153 | val_ 154 | end 155 | end 156 | end 157 | 158 | # The first field has the default value of 1. All other fields 159 | # have a default value of 0. Thus, the default version number 160 | # overall is "1.0". 161 | field(:field0, :type => :integer, :default_value => 1) do 162 | field(:field1, :type => :string) do 163 | field(:field2, :type => :string) do 164 | field(:field3, :type => :string) do 165 | field(:field4, :type => :string) do 166 | field(:field5, :type => :string) do 167 | field(:field6, :type => :string) do 168 | field(:field7, :type => :string) 169 | end 170 | end 171 | end 172 | end 173 | end 174 | end 175 | end 176 | 177 | # Some field aliases providing alternate names for major fields 178 | alias_field(:major, :field0) 179 | alias_field(:minor, :field1) 180 | 181 | # Add the methods in this module to each value 182 | add_module(Format::Rubygems::ExtraMethods) 183 | end 184 | 185 | # The following is the definition of the rubygems format. It 186 | # understands the rubygems schema defined above. 187 | Format::Delimiter.new(schema_) do 188 | 189 | # All version number strings must start with the major version. 190 | # Unlike other fields, it is not preceded by any delimiter. 191 | field(:field0) do 192 | recognize_number(:delimiter_regexp => '', :default_delimiter => '') 193 | end 194 | 195 | # The remainder of the version number are represented as strings 196 | # or integers delimited by periods by default. Each is also 197 | # dependent on the presence of the previous field, so 198 | # :requires_previous_field retains its default value of true. 199 | # Finally, they can be optional in an unparsed string if they are 200 | # set to the default value of 0. 201 | field(:field1) do 202 | recognize_regexp('[0-9a-zA-Z]+', :default_value_optional => true) 203 | end 204 | field(:field2) do 205 | recognize_regexp('[0-9a-zA-Z]+', :default_value_optional => true) 206 | end 207 | field(:field3) do 208 | recognize_regexp('[0-9a-zA-Z]+', :default_value_optional => true) 209 | end 210 | field(:field4) do 211 | recognize_regexp('[0-9a-zA-Z]+', :default_value_optional => true) 212 | end 213 | field(:field5) do 214 | recognize_regexp('[0-9a-zA-Z]+', :default_value_optional => true) 215 | end 216 | field(:field6) do 217 | recognize_regexp('[0-9a-zA-Z]+', :default_value_optional => true) 218 | end 219 | field(:field7) do 220 | recognize_regexp('[0-9a-zA-Z]+', :default_value_optional => true) 221 | end 222 | 223 | # By default, we require that at least the first two fields 224 | # appear in an unparsed version string. 225 | default_unparse_params(:required_fields => [:field1]) 226 | end 227 | end 228 | 229 | 230 | end 231 | 232 | 233 | register('rubygems', Format::Rubygems.create, true) 234 | 235 | 236 | end 237 | 238 | 239 | module Conversion 240 | 241 | 242 | # This is a namespace for the implementation of the conversion between 243 | # the rubygems and standard formats. 244 | 245 | module Rubygems 246 | 247 | 248 | # Create the conversion from standard to rubygems format. 249 | # This method is called internally when Versionomy loads the rubygems 250 | # format, and you should not need to call it again. It is documented 251 | # so that you can inspect its source code from RDoc, since the source 252 | # contains useful examples of how to use the conversion DSLs. 253 | 254 | def self.create_standard_to_rubygems 255 | 256 | # We'll use a parsing conversion. 257 | Conversion::Parsing.new do 258 | 259 | # We're going to modify how the standard format version is 260 | # unparsed, so the rubygems format will have a better chance 261 | # of parsing it. 262 | to_modify_unparse_params do |params_, convert_params_| 263 | 264 | params_ ||= {} 265 | 266 | # If the standard format version has a prerelease notation, 267 | # make sure it is set off using a delimiter that the rubygems 268 | # format can recognize. So instead of "1.0b2", we force the 269 | # unparsing to generate "1.0.b.2". 270 | params_[:release_type_delim] = '.' 271 | params_[:development_version_delim] = '.' 272 | params_[:alpha_version_delim] = '.' 273 | params_[:beta_version_delim] = '.' 274 | params_[:release_candidate_version_delim] = '.' 275 | params_[:preview_version_delim] = '.' 276 | 277 | # If the standard format version has a patchlevel notation, 278 | # force it to use the default number rather than letter style. 279 | # So instead of "1.2c", we force the unparsing to generate 280 | # "1.2-3". 281 | params_[:patchlevel_style] = nil 282 | 283 | # If the standard format version has a patchlevel notation, 284 | # force it to use the default delimiter of "-" so the rubygems 285 | # format will recognize it. So instead of "1.9.1p243", we force 286 | # the unparsing to generate "1.9.1-243". 287 | params_[:patchlevel_delim] = nil 288 | 289 | # If the standard format version includes a "v" prefix, strip 290 | # it because rubygems doesn't like it. 291 | params_[:major_delim] = nil 292 | 293 | params_ 294 | end 295 | 296 | # Standard formats sometimes allow hyphens and spaces in field 297 | # delimiters, but the rubygems format requires periods. So modify 298 | # the unparsed string to conform to rubygems's expectations. 299 | to_modify_string do |str_, convert_params_| 300 | str_.gsub(/[\.\s-]+/, '.') 301 | end 302 | 303 | end 304 | 305 | end 306 | 307 | 308 | # Create the conversion from rubygems to standard format. 309 | # This method is called internally when Versionomy loads the rubygems 310 | # format, and you should not need to call it again. It is documented 311 | # so that you can inspect its source code from RDoc, since the source 312 | # contains useful examples of how to use the conversion DSLs. 313 | 314 | def self.create_rubygems_to_standard 315 | 316 | # We'll use a parsing conversion. 317 | Conversion::Parsing.new do 318 | 319 | # Handle the case where the rubygems version ends with a string 320 | # field, e.g. "1.0.b". We want to treat this like "1.0b0" rather 321 | # than "1.0-2" since the rubygems semantic states that this is a 322 | # prerelease version. So we add 0 to the end of the parsed string 323 | # if it ends in a letter. 324 | to_modify_string do |str_, convert_params_| 325 | str_.gsub(/([[:alpha:]])\z/, '\10') 326 | end 327 | 328 | end 329 | 330 | end 331 | 332 | 333 | end 334 | 335 | 336 | register(:standard, :rubygems, Conversion::Rubygems.create_standard_to_rubygems, true) 337 | register(:rubygems, :standard, Conversion::Rubygems.create_rubygems_to_standard, true) 338 | 339 | 340 | end 341 | 342 | 343 | end 344 | -------------------------------------------------------------------------------- /test/tc_standard_parse.rb: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # 3 | # Versionomy parsing tests on standard schema 4 | # 5 | # This file contains tests for parsing on the standard schema 6 | # 7 | # ----------------------------------------------------------------------------- 8 | # Copyright 2008 Daniel Azuma 9 | # 10 | # All rights reserved. 11 | # 12 | # Redistribution and use in source and binary forms, with or without 13 | # modification, are permitted provided that the following conditions are met: 14 | # 15 | # * Redistributions of source code must retain the above copyright notice, 16 | # this list of conditions and the following disclaimer. 17 | # * Redistributions in binary form must reproduce the above copyright notice, 18 | # this list of conditions and the following disclaimer in the documentation 19 | # and/or other materials provided with the distribution. 20 | # * Neither the name of the copyright holder, nor the names of any other 21 | # contributors to this software, may be used to endorse or promote products 22 | # derived from this software without specific prior written permission. 23 | # 24 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 28 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 | # POSSIBILITY OF SUCH DAMAGE. 35 | # ----------------------------------------------------------------------------- 36 | 37 | 38 | require 'minitest/autorun' 39 | require 'versionomy' 40 | 41 | 42 | module Versionomy 43 | module Tests # :nodoc: 44 | 45 | class TestStandardParse < ::Minitest::Test # :nodoc: 46 | 47 | 48 | # Test parsing full. 49 | 50 | def test_parsing_full_release 51 | value_ = ::Versionomy.parse('2.0.1.1-4.6') 52 | assert_equal(2, value_.major) 53 | assert_equal(0, value_.minor) 54 | assert_equal(1, value_.tiny) 55 | assert_equal(1, value_.tiny2) 56 | assert_equal(:final, value_.release_type) 57 | assert_equal(4, value_.patchlevel) 58 | assert_equal(6, value_.patchlevel_minor) 59 | assert_equal('2.0.1.1-4.6', value_.unparse) 60 | end 61 | 62 | 63 | # Test parsing abbreviated. 64 | 65 | def test_parsing_abbrev_release 66 | value_ = ::Versionomy.parse('2.0.1') 67 | assert_equal(2, value_.major) 68 | assert_equal(0, value_.minor) 69 | assert_equal(1, value_.tiny) 70 | assert_equal(0, value_.tiny2) 71 | assert_equal(:final, value_.release_type) 72 | assert_equal(0, value_.patchlevel) 73 | assert_equal(0, value_.patchlevel_minor) 74 | assert_equal('2.0.1.0-0.0', value_.unparse(:required_fields => [:minor, :tiny, :tiny2, :patchlevel, :patchlevel_minor])) 75 | assert_equal('2.0.1-0', value_.unparse(:required_fields => [:minor, :patchlevel])) 76 | assert_equal('2.0.1', value_.unparse) 77 | end 78 | 79 | 80 | # Test parsing with trailing zeros. 81 | 82 | def test_parsing_trailing_zeros 83 | value_ = ::Versionomy.parse('2.0.0') 84 | assert_equal(2, value_.major) 85 | assert_equal(0, value_.minor) 86 | assert_equal(0, value_.tiny) 87 | assert_equal(0, value_.tiny2) 88 | assert_equal(:final, value_.release_type) 89 | assert_equal(0, value_.patchlevel) 90 | assert_equal(0, value_.patchlevel_minor) 91 | assert_equal('2.0.0', value_.unparse) 92 | end 93 | 94 | 95 | # Test parsing with leading zeros on a field. 96 | 97 | def test_parsing_field_leading_zeros 98 | value_ = ::Versionomy.parse('2.09') 99 | assert_equal(2, value_.major) 100 | assert_equal(9, value_.minor) 101 | assert_equal(0, value_.tiny) 102 | assert_equal('2.09', value_.unparse) 103 | value_ = value_.bump(:minor) 104 | assert_equal(10, value_.minor) 105 | assert_equal('2.10', value_.unparse) 106 | value_ = value_.change(:minor => 123) 107 | assert_equal(123, value_.minor) 108 | assert_equal('2.123', value_.unparse) 109 | value_ = value_.change(:minor => 4) 110 | assert_equal(4, value_.minor) 111 | assert_equal('2.04', value_.unparse) 112 | value_ = ::Versionomy.parse('2.00') 113 | assert_equal(0, value_.minor) 114 | assert_equal('2.00', value_.unparse) 115 | end 116 | 117 | 118 | # Test unparsing with a minimum width. 119 | 120 | def test_unparsing_minimum_width_field 121 | value_ = ::Versionomy.parse('2.0') 122 | assert_equal('2.0', value_.unparse) 123 | assert_equal('2.00', value_.unparse(:minor_width => 2)) 124 | assert_equal('02.0', value_.unparse(:major_width => 2)) 125 | value_ = value_.bump(:minor) 126 | assert_equal('2.1', value_.unparse) 127 | assert_equal('2.01', value_.unparse(:minor_width => 2)) 128 | value_ = value_.change(:minor => 12) 129 | assert_equal('2.12', value_.unparse) 130 | assert_equal('2.12', value_.unparse(:minor_width => 1)) 131 | end 132 | 133 | 134 | # Test parsing major version only. 135 | 136 | def test_parsing_major_only 137 | value_ = ::Versionomy.parse('2') 138 | assert_equal(2, value_.major) 139 | assert_equal(0, value_.minor) 140 | assert_equal(0, value_.tiny) 141 | assert_equal(0, value_.tiny2) 142 | assert_equal(:final, value_.release_type) 143 | assert_equal(0, value_.patchlevel) 144 | assert_equal(0, value_.patchlevel_minor) 145 | assert_equal('2', value_.unparse) 146 | end 147 | 148 | 149 | # Test parsing preview. 150 | 151 | def test_parsing_preview 152 | value_ = ::Versionomy.parse('2.0.1pre4') 153 | assert_equal(2, value_.major) 154 | assert_equal(0, value_.minor) 155 | assert_equal(1, value_.tiny) 156 | assert_equal(0, value_.tiny2) 157 | assert_equal(:preview, value_.release_type) 158 | assert_equal(4, value_.preview_version) 159 | assert_equal(0, value_.preview_minor) 160 | assert_equal('2.0.1pre4', value_.unparse) 161 | assert_equal('2.0.1pre4.0', value_.unparse(:required_fields => [:preview_minor])) 162 | end 163 | 164 | 165 | # Test parsing alpha. 166 | 167 | def test_parsing_alpha 168 | value_ = ::Versionomy.parse('2.0.1a4.1') 169 | assert_equal(2, value_.major) 170 | assert_equal(0, value_.minor) 171 | assert_equal(1, value_.tiny) 172 | assert_equal(0, value_.tiny2) 173 | assert_equal(:alpha, value_.release_type) 174 | assert_equal(4, value_.alpha_version) 175 | assert_equal(1, value_.alpha_minor) 176 | assert_equal('2.0.1a4.1', value_.unparse) 177 | assert_equal('2.0.1a4.1', value_.unparse(:optional_fields => [:alpha_minor])) 178 | end 179 | 180 | 181 | # Test parsing beta. 182 | 183 | def test_parsing_beta 184 | value_ = ::Versionomy.parse('2.52.1b4.0') 185 | assert_equal(2, value_.major) 186 | assert_equal(52, value_.minor) 187 | assert_equal(1, value_.tiny) 188 | assert_equal(0, value_.tiny2) 189 | assert_equal(:beta, value_.release_type) 190 | assert_equal(4, value_.beta_version) 191 | assert_equal(0, value_.beta_minor) 192 | assert_equal('2.52.1b4.0', value_.unparse) 193 | assert_equal('2.52.1b4', value_.unparse(:optional_fields => [:beta_minor])) 194 | end 195 | 196 | 197 | # Test parsing beta alternates 198 | 199 | def test_parsing_beta_alternates 200 | assert_equal(::Versionomy.parse('2.52.1 beta4'), '2.52.1b4') 201 | assert_equal(::Versionomy.parse('2.52.1-b4'), '2.52.1b4') 202 | assert_equal(::Versionomy.parse('2.52.1_b4'), '2.52.1b4') 203 | assert_equal(::Versionomy.parse('2.52.1.b4'), '2.52.1b4') 204 | assert_equal(::Versionomy.parse('2.52.1B4'), '2.52.1b4') 205 | assert_equal(::Versionomy.parse('2.52.1BETA4'), '2.52.1b4') 206 | assert_equal(::Versionomy.parse('2.52.1 Beta4'), '2.52.1b4') 207 | assert_equal(::Versionomy.parse('2.52.1 eta4', :extra_characters => :ignore), '2.52.1') 208 | assert_equal(::Versionomy.parse('2.52.1 Beta'), '2.52.1b0') 209 | end 210 | 211 | 212 | # Test parsing release candidate. 213 | 214 | def test_parsing_release_candidate 215 | value_ = ::Versionomy.parse('0.2rc0') 216 | assert_equal(0, value_.major) 217 | assert_equal(2, value_.minor) 218 | assert_equal(0, value_.tiny) 219 | assert_equal(0, value_.tiny2) 220 | assert_equal(:release_candidate, value_.release_type) 221 | assert_equal(0, value_.release_candidate_version) 222 | assert_equal(0, value_.release_candidate_minor) 223 | assert_equal('0.2rc0', value_.unparse) 224 | assert_equal('0.2rc0.0', value_.unparse(:required_fields => [:release_candidate_minor])) 225 | assert_equal('0.2rc', value_.unparse(:optional_fields => [:release_candidate_version])) 226 | end 227 | 228 | 229 | # Test parsing release candidate changing to other prerelease. 230 | # Ensures that :short style takes precedence over :long for parsing "rc". 231 | 232 | def test_parsing_release_candidate_type_change 233 | value_ = ::Versionomy.parse('0.2rc1') 234 | assert_equal(:release_candidate, value_.release_type) 235 | assert_equal(1, value_.release_candidate_version) 236 | assert_equal('0.2rc1', value_.unparse) 237 | value_ = value_.change(:release_type => :beta) 238 | assert_equal(:beta, value_.release_type) 239 | assert_equal(1, value_.beta_version) 240 | assert_equal('0.2b1', value_.unparse) 241 | value_ = value_.change({:release_type => :beta}, :release_type_style => :long) 242 | assert_equal(:beta, value_.release_type) 243 | assert_equal(1, value_.beta_version) 244 | assert_equal('0.2beta1', value_.unparse) 245 | end 246 | 247 | 248 | # Test parsing forms without a prerelease version 249 | 250 | def test_parsing_without_prerelease_version 251 | value_ = ::Versionomy.parse('1.9.2dev') 252 | assert_equal(value_.release_type, :development) 253 | assert_equal(value_.development_version, 0) 254 | assert_equal('1.9.2dev', value_.to_s) 255 | value_ = value_.bump(:development_version) 256 | assert_equal('1.9.2dev1', value_.to_s) 257 | end 258 | 259 | 260 | # Test parsing forms without a prerelease version. 261 | # Ensures that :development_version prefers to be required. 262 | 263 | def test_unparsing_prerelease_version_0 264 | value_ = ::Versionomy.parse('1.9.2dev1') 265 | assert_equal(value_.release_type, :development) 266 | assert_equal(value_.development_version, 1) 267 | assert_equal('1.9.2dev1', value_.to_s) 268 | value2_ = value_.change(:development_version => 0) 269 | assert_equal('1.9.2dev0', value2_.to_s) 270 | value2_ = value_.change({:development_version => 0}, :optional_fields => [:development_version]) 271 | assert_equal('1.9.2dev', value2_.to_s) 272 | end 273 | 274 | 275 | # Test unparsing a value that requires lookback. 276 | 277 | def test_unparsing_with_lookback 278 | value_ = ::Versionomy.parse('2.0') 279 | value2_ = value_.change(:tiny2 => 1) 280 | assert_equal(1, value2_.tiny2) 281 | assert_equal('2.0.0.1', value2_.unparse) 282 | value3_ = value2_.change(:tiny2 => 0) 283 | assert_equal(0, value3_.tiny2) 284 | assert_equal('2.0', value3_.unparse) 285 | end 286 | 287 | 288 | # Test delimiter changes in a multi-form field. 289 | 290 | def test_multi_form_delimiter_changes 291 | value_ = ::Versionomy.parse('2.0 preview 1') 292 | assert_equal('2.0 preview 1', value_.to_s) 293 | value2_ = value_.change(:release_type => :final) 294 | assert_equal('2.0', value2_.to_s) 295 | value3_ = value2_.change(:release_type => :preview, :preview_version => 1) 296 | assert_equal('2.0 preview 1', value3_.to_s) 297 | end 298 | 299 | 300 | # Test different patchlevel separators. 301 | 302 | def test_patchlevel_separators 303 | expected_ = [1,9,1,0,:final,243,0] 304 | assert_equal(expected_, ::Versionomy.parse('1.9.1-p243').values_array) 305 | assert_equal(expected_, ::Versionomy.parse('1.9.1_p243').values_array) 306 | assert_equal(expected_, ::Versionomy.parse('1.9.1_u243').values_array) 307 | assert_equal(expected_, ::Versionomy.parse('1.9.1p243').values_array) 308 | assert_equal(expected_, ::Versionomy.parse('1.9.1.p243').values_array) 309 | assert_equal(expected_, ::Versionomy.parse('1.9.1 p243').values_array) 310 | assert_equal(expected_, ::Versionomy.parse('1.9.1-243').values_array) 311 | assert_equal(expected_, ::Versionomy.parse('1.9.1_243').values_array) 312 | end 313 | 314 | 315 | # Test alphabetic patchlevels. 316 | # In particular, make sure the parser can distinguish between these 317 | # and the markers for prereleases. 318 | 319 | def test_patchlevel_alphabetic 320 | value_ = ::Versionomy.parse('1.9a') 321 | assert_equal([1, 9, 0, 0, :final, 1, 0], value_.values_array) 322 | assert_equal('1.9a', value_.to_s) 323 | value_ = ::Versionomy.parse('1.9b') 324 | assert_equal([1, 9, 0, 0, :final, 2, 0], value_.values_array) 325 | assert_equal('1.9b', value_.to_s) 326 | value_ = ::Versionomy.parse('1.9d') 327 | assert_equal([1, 9, 0, 0, :final, 4, 0], value_.values_array) 328 | assert_equal('1.9d', value_.to_s) 329 | value_ = ::Versionomy.parse('1.9p') 330 | assert_equal([1, 9, 0, 0, :final, 16, 0], value_.values_array) 331 | assert_equal('1.9p', value_.to_s) 332 | value_ = ::Versionomy.parse('1.9r') 333 | assert_equal([1, 9, 0, 0, :final, 18, 0], value_.values_array) 334 | assert_equal('1.9r', value_.to_s) 335 | value_ = ::Versionomy.parse('1.9u') 336 | assert_equal([1, 9, 0, 0, :final, 21, 0], value_.values_array) 337 | assert_equal('1.9u', value_.to_s) 338 | end 339 | 340 | 341 | # Test setting delimiters on unparse, including testing for illegal delimiters 342 | 343 | def test_unparse_with_custom_delimiters 344 | value_ = ::Versionomy.parse('1.2b3') 345 | assert_equal('1.2.b.3', value_.unparse(:release_type_delim => '.', :beta_version_delim => '.')) 346 | assert_equal('1.2b3', value_.unparse(:release_type_delim => '=', :beta_version_delim => '*')) 347 | value_ = ::Versionomy.parse('1.2-4') 348 | assert_equal('1.2-4', value_.unparse(:release_type_delim => '.')) 349 | end 350 | 351 | 352 | # Test java version formats 353 | 354 | def test_java_formats 355 | value_ = ::Versionomy.parse('1.6.0_17') 356 | assert_equal([1, 6, 0, 0, :final, 17, 0], value_.values_array) 357 | assert_equal('1.6.0_17', value_.to_s) 358 | value_ = ::Versionomy.parse('6u17') 359 | assert_equal([6, 0, 0, 0, :final, 17, 0], value_.values_array) 360 | assert_equal('6u17', value_.to_s) 361 | end 362 | 363 | 364 | # Test formats prefixed with "v" 365 | 366 | def test_v_prefix 367 | value_ = ::Versionomy.parse('v1.2') 368 | assert_equal([1, 2, 0, 0, :final, 0, 0], value_.values_array) 369 | assert_equal('v1.2', value_.to_s) 370 | value_ = ::Versionomy.parse('V 2.3') 371 | assert_equal([2, 3, 0, 0, :final, 0, 0], value_.values_array) 372 | assert_equal('V 2.3', value_.to_s) 373 | end 374 | 375 | 376 | # Test parse errors 377 | 378 | def test_parsing_errors 379 | assert_raises(::Versionomy::Errors::ParseError) do 380 | ::Versionomy.parse('2.52.1 eta4') 381 | end 382 | end 383 | 384 | 385 | end 386 | 387 | end 388 | end 389 | -------------------------------------------------------------------------------- /Versionomy.rdoc: -------------------------------------------------------------------------------- 1 | == Versionomy 2 | 3 | Versionomy is a generalized version number library. 4 | It provides tools to represent, manipulate, parse, and compare version 5 | numbers in the wide variety of versioning schemes in use. 6 | 7 | This document provides a step-by-step introduction to most of the features 8 | of Versionomy. 9 | 10 | === Version numbers done right? 11 | 12 | Let's be honest. Version numbers are not easy to deal with, and very 13 | seldom seem to be done right. 14 | 15 | Imagine the common case of testing the Ruby version. Most of us, if we 16 | need to worry about Ruby VM compatibility, will do something like: 17 | 18 | do_something if RUBY_VERSION >= "1.8.7" 19 | 20 | Treating the version number as a string works well enough, until it 21 | doesn't. The above code will do the right thing for Ruby 1.8.6, 1.8.7, 22 | 1.8.8, and 1.9.1. But it will fail if the version is "1.8.10" or "1.10". 23 | And properly interpreting "prerelease" version syntax such as 24 | "1.9.2-preview1"? Forget it. 25 | 26 | There are a few version number classes out there that do better than 27 | treating version numbers as plain strings. One example is Gem::Version, 28 | part of the RubyGems package. This class separates the version into fields 29 | and lets you manipulate and compare version numbers more robustly. It even 30 | provides limited support for "prerelease" versions through using string- 31 | valued fields-- although it's a hack, and a bit of a clumsy one at that. A 32 | prerelease version has to be represented like this: "1.9.2.b.1" or 33 | "1.9.2.preview.2". Wouldn't it be nice to be able to parse more typical 34 | version number formats such as "1.9.2b1" and "1.9.2-preview2"? Wouldn't 35 | it be nice for a version like "1.9.2b1" to _understand_ that it's a "beta" 36 | version and behave accordingly? 37 | 38 | With Versionomy, you can do all this and more. Here's how... 39 | 40 | === Creating version numbers 41 | 42 | Creating a version number object in Versionomy is as simple as passing a 43 | string to a factory. Versionomy understands a wide range of version number 44 | formats out of the box. 45 | 46 | v1 = Versionomy.parse('1.2') # Simple version numbers 47 | v2 = Versionomy.parse('2.1.5.0') # Up to four fields supported 48 | v3 = Versionomy.parse('1.9b3') # Alpha and beta versions 49 | v4 = Versionomy.parse('1.9rc2') # Release candidates too 50 | v5 = Versionomy.parse('1.9.2-preview2') # Preview releases 51 | v6 = Versionomy.parse('1.9.2-p6') # Patchlevels 52 | v7 = Versionomy.parse('v2.0 beta 6.1') # Many alternative syntaxes 53 | 54 | You can also construct version numbers manually by passing a hash of field 55 | values. See the next section for a discussion of fields. 56 | 57 | v1 = Versionomy.create(:major => 1, :minor => 2) # creates version "1.2" 58 | v2 = Versionomy.create(:major => 1, :minor => 9, 59 | :release_type => :beta, :beta_version => 3) # creates version "1.9b3" 60 | 61 | The current ruby virtual machine version can be obtained using: 62 | 63 | v1 = Versionomy.ruby_version 64 | 65 | Many other libraries include their version as a string constant in their 66 | main namespace module. Versionomy provides a quick facility to attempt to 67 | extract the version of a library: 68 | 69 | require 'nokogiri' 70 | v1 = Versionomy.version_of(Nokogiri) 71 | 72 | === Version number fields 73 | 74 | A version number is a collection of fields in a particular order. Standard 75 | version numbers have the following fields: 76 | 77 | * :major 78 | * :minor 79 | * :tiny 80 | * :tiny2 81 | * :release_type 82 | 83 | The first four fields correspond to the four numeric fields of the version 84 | number. E.g. version numbers have the form "major.minor.tiny.tiny2". 85 | Trailing fields that have a zero value may be omitted from a string 86 | representation, but are still present in the Versionomy::Value object. 87 | 88 | The fifth field is special. Its value is one of the following symbols: 89 | 90 | * :development 91 | * :alpha 92 | * :beta 93 | * :release_candidate 94 | * :preview 95 | * :final 96 | 97 | The value of the :release_type field determines which other fields are 98 | available in the version number. If the :release_type is :development, then 99 | the two fields :development_version and :development_minor are available. 100 | Similarly, if :release_type is :alpha, then the two fields :alpha_version 101 | and :alpha_minor are available, and so on. If :release_type is :final, that 102 | exposes the two fields :patchlevel and :patchlevel_minor. 103 | 104 | You can query a field value simply by calling a method on the value: 105 | 106 | v1 = Versionomy.parse('1.2b3') 107 | v1.major # => 1 108 | v1.minor # => 2 109 | v1.tiny # => 0 110 | v1.tiny2 # => 0 111 | v1.release_type # => :beta 112 | v1.beta_version # => 3 113 | v1.beta_minor # => 0 114 | v1.release_candidate_version # raises NoMethodError 115 | 116 | The above fields are merely the standard fields that Versionomy provides 117 | out of the box. Versionomy also provides advanced users the ability to 118 | define new version "schemas" with any number of different fields and 119 | different semantics. See the RDocs for Versionomy::Schema for more 120 | information. 121 | 122 | === Version number calculations 123 | 124 | Version numbers can be compared (and thus sorted). Versionomy knows how to 125 | handle prerelease versions and patchlevels correctly. It also compares the 126 | semantic value so even if versions use an alternate syntax, they will be 127 | compared correctly. Each of these expressions evaluates to true: 128 | 129 | Versionomy.parse('1.2') < Versionomy.parse('1.10') 130 | Versionomy.parse('1.2') > Versionomy.parse('1.2b3') 131 | Versionomy.parse('1.2b3') > Versionomy.parse('1.2a4') 132 | Versionomy.parse('1.2') < Versionomy.parse('1.2-p1') 133 | Versionomy.parse('1.2') == Versionomy.parse('1.2-p0') 134 | Versionomy.parse('1.2b3') == Versionomy.parse('1.2.0-beta3') 135 | 136 | Versionomy automatically converts (parses) strings when comparing with a 137 | version number, so you could even evaluate these: 138 | 139 | Versionomy.parse('1.2') < '1.10' 140 | Versionomy::VERSION > '0.2' 141 | 142 | The Versionomy API provides various methods for manipulating fields such as 143 | bumping, resetting to default, and changing to an arbitrary value. Version 144 | numbers are always immutable, so changing a version number always produces 145 | a copy. Below are a few examples. See the RDocs for the class 146 | Versionomy::Value for more details. 147 | 148 | v_orig = Versionomy.parse('1.2b3') 149 | v1 = v_orig.change(:beta_version => 4) # creates version "1.2b4" 150 | v2 = v_orig.change(:tiny => 4) # creates version "1.2.4b3" 151 | v3 = v_orig.bump(:minor) # creates version "1.3" 152 | v4 = v_orig.bump(:release_type) # creates version "1.2rc1" 153 | v5 = v_orig.reset(:minor) # creates version "1.0" 154 | 155 | A few more common calculations are also provided: 156 | 157 | v_orig = Versionomy.parse('1.2b3') 158 | v_orig.prerelease? # => true 159 | v6 = v_orig.release # creates version "1.2" 160 | 161 | === Parsing and unparsing 162 | 163 | Versionomy's parsing and unparsing services appear simple from the outside, 164 | but a closer look reveals some sophisticated features. Parsing is as simple 165 | as passing a string to Versionomy#parse, and unparsing is as simple as 166 | calling Versionomy::Value#unparse or Versionomy::Value#to_s. 167 | 168 | v = Versionomy.parse('1.2b3') # Create a Versionomy::Value 169 | v.unparse # => "1.2b3" 170 | 171 | Versionomy does its best to preserve the original syntax when parsing a 172 | version string, so that syntax can be used when unparsing. 173 | 174 | v1 = Versionomy.parse('1.2b3') 175 | v2 = Versionomy.parse('1.2.0-beta3') 176 | v1 == b2 # => true 177 | v1.unparse # => "1.2b3" 178 | v2.unparse # => "1.2.0-beta3" 179 | 180 | Versionomy even preserves the original syntax when changing a value: 181 | 182 | v1 = Versionomy.parse('1.2b3') 183 | v2 = Versionomy.parse('1.2.0.0b3') 184 | v1 == v2 # => true 185 | v1r = v1.release 186 | v2r = v2.release 187 | v1r == v2r # => true 188 | v1r.unparse # => "1.2" 189 | v2r.unparse # => "1.2.0.0" 190 | 191 | You can change the settings manually when unparsing a value. 192 | 193 | v1 = Versionomy.parse('1.2b3') 194 | v1.unparse # => "1.2b3" 195 | v1.unparse(:required_fields => :tiny) # => "1.2.0b3" 196 | v1.unparse(:release_type_delim => '-', 197 | :release_type_style => :long) # => "1.2-beta3" 198 | 199 | Versionomy also supports serialization using Marshal and YAML. 200 | 201 | require 'yaml' 202 | v1 = Versionomy.parse('1.2b3') 203 | v1.unparse # => "1.2b3" 204 | str = v1.to_yaml 205 | v2 = YAML.load(str) 206 | v2.unparse # => "1.2b3" 207 | 208 | === Customized formats 209 | 210 | Although the standard parser used by Versionomy is likely sufficient for 211 | most common syntaxes, Versionomy also lets you customize the parser for an 212 | unusual syntax. Here is an example of a customized formatter for version 213 | numbers used by a certain large software company: 214 | 215 | year_sp_format = Versionomy.default_format.modified_copy do 216 | field(:minor) do 217 | recognize_number(:default_value_optional => true, 218 | :delimiter_regexp => '\s?sp', 219 | :default_delimiter => ' SP') 220 | end 221 | end 222 | v1 = year_sp_format.parse('2008 SP2') 223 | v1.major # => 2008 224 | v1.minor # => 2 225 | v1.unparse # => "2008 SP2" 226 | v1 == "2008.2" # => true 227 | v2 = v1.bump(:minor) 228 | v2.unparse # => "2008 SP3" 229 | 230 | The above example uses a powerful DSL provided by Versionomy to create a 231 | specialized parser. In most cases, this DSL will be powerful enough to 232 | handle your parsing needs; in fact Versionomy's entire standard parser is 233 | written using the DSL. However, in case you need to parse very unusual 234 | syntax, you can also write an arbitrary parser. See the RDocs for the 235 | Versionomy::Format::Delimiter class for more information on the DSL. See 236 | the RDocs for the Versionomy::Format::Base class for information on the 237 | interface you need to implement to write an arbitrary parser. 238 | 239 | If you create a format, you can register it with Versionomy and provide a 240 | name for it. This will allow you to reference it easily, as well as allow 241 | Versionomy to serialize versions created with your custom format. See the 242 | RDocs for the Versionomy::Format module for more information. 243 | 244 | Versionomy::Format.register("bigcompany.versionformat", year_sp_format) 245 | v1 = Versionomy.parse("2009 SP1", "bigcompany.versionformat") 246 | 247 | Note that versions in the year_sp_format example can be compared with 248 | versions using the standard parser. This is because the versions actually 249 | share the same schema-- that is, they have the same fields. We have merely 250 | changed the parser. 251 | 252 | Recall that it is also possible to change the schema (the fields). This is 253 | also done via a DSL (see the Versionomy::Schema module and its contents). 254 | Version numbers with different schemas cannot normally be compared, because 255 | they have different fields and different semantics. You can, however, 256 | define ways to convert version numbers from one schema to another. See the 257 | Versionomy::Conversion module and its contents for details. 258 | 259 | Versionomy provides an example of a custom schema with its own custom 260 | format, designed to mimic the Rubygems version class. This can be accessed 261 | using the format registered under the name "rubygems". Conversion functions 262 | are also provided between the rubygems and standard schemas. 263 | 264 | v1 = Versionomy.parse("1.2b3") # Standard schema/format 265 | v2 = Versionomy.parse("1.2.b.4", :rubygems) # Rubygems schema/format 266 | v2.field0 # => 1 267 | # (Rubygems fields have different names) 268 | v1a = v1.convert(:rubygems) # creates rubygems version "1.2.b.3" 269 | v2a = v2.convert(:standard) # creates standard version "1.2b4" 270 | v1 < v2 # => true 271 | # (Schemas are different but Versionomy 272 | # autoconverts if possible) 273 | v2 < v1 # => false 274 | v3 = Versionomy.parse("1.2.foo", :rubygems) # rubygems schema/format 275 | v3a = v3.convert(:standard) # raises Versionomy::Errors::ConversionError 276 | # (Value not convertable to standard) 277 | v1 < v3 # raises Versionomy::Errors::SchemaMismatchError 278 | # (Autoconversion failed) 279 | v3 > v1 # => true 280 | # (Autoconversion is attempted only on the 281 | # the second value, and this one succeeds.) 282 | 283 | The APIs for defining schemas, formats, and conversions are rather complex. 284 | I recommend looking through the examples in the modules 285 | Versionomy::Format::Standard, Versionomy::Format::Rubygems, and 286 | Versionomy::Conversion::Rubygems for further information. 287 | 288 | === Requirements 289 | 290 | * Ruby 1.9.3 or later, JRuby 1.5 or later, or Rubinius 1.0 or later. 291 | * blockenspiel 0.5.0 or later. 292 | 293 | === Installation 294 | 295 | gem install versionomy 296 | 297 | === Known issues and limitations 298 | 299 | * Test coverage is still a little skimpy. It is focused on the "standard" 300 | version number format and schema, but doesn't fully exercise all the 301 | capabilities of custom formats. 302 | 303 | === Development and support 304 | 305 | Documentation is available at http://dazuma.github.com/versionomy/rdoc 306 | 307 | Source code is hosted on Github at http://github.com/dazuma/versionomy 308 | 309 | Contributions are welcome. Fork the project on Github. 310 | 311 | Build status: {}[http://travis-ci.org/dazuma/versionomy] 312 | 313 | Report bugs on Github issues at http://github.org/dazuma/versionomy/issues 314 | 315 | Contact the author at dazuma at gmail dot com. 316 | 317 | === Author / Credits 318 | 319 | Versionomy is written by Daniel Azuma (http://www.daniel-azuma.com/). 320 | 321 | == LICENSE: 322 | 323 | Copyright 2008 Daniel Azuma. 324 | 325 | All rights reserved. 326 | 327 | Redistribution and use in source and binary forms, with or without 328 | modification, are permitted provided that the following conditions are met: 329 | 330 | * Redistributions of source code must retain the above copyright notice, 331 | this list of conditions and the following disclaimer. 332 | * Redistributions in binary form must reproduce the above copyright notice, 333 | this list of conditions and the following disclaimer in the documentation 334 | and/or other materials provided with the distribution. 335 | * Neither the name of the copyright holder, nor the names of any other 336 | contributors to this software, may be used to endorse or promote products 337 | derived from this software without specific prior written permission. 338 | 339 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 340 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 341 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 342 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 343 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 344 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 345 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 346 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 347 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 348 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 349 | POSSIBILITY OF SUCH DAMAGE. 350 | --------------------------------------------------------------------------------