├── .gitignore ├── .rspec ├── Gemfile ├── History.rdoc ├── License.txt ├── README.rdoc ├── Rakefile ├── TODO.rdoc ├── VERSION ├── lib ├── rake │ └── version_task.rb ├── version.rb └── version │ ├── component.rb │ └── ext │ ├── array.rb │ ├── hash.rb │ ├── module.rb │ └── string.rb ├── spec ├── spec.opts ├── spec_helper.rb └── version_spec.rb └── version.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | 3 | /doc/* 4 | /pkg/* 5 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /History.rdoc: -------------------------------------------------------------------------------- 1 | === Version 0.9.2 / 2010-02-23 2 | 3 | * bug fixes 4 | * Ruby 1.8.6 support (closes #5) 5 | 6 | === Version 0.9.1 / 2010-02-17 7 | 8 | * enhancements 9 | * automagic gemspec generation 10 | 11 | === Version 0.9.0 / 2010-02-15 12 | 13 | * bug fixes 14 | * fixed project name in some areas 15 | * ensure correct sorting of 1.9 <=> 1.10 (closes #3) 16 | * ensure correct sorting of prerelease versions 17 | * Version.current returns nil if no VERSION found (closes #4) 18 | 19 | * enhancements 20 | * Version#prerelease? to determine if it's a prerelease version 21 | * Version#bump! now accepts symbolic indexes 22 | * Version#inspect has slightly friendlier output 23 | * VersionTask now correctly bumps prerelease versions 24 | * VersionTask has new version:bump:pre task 25 | * now properly MIT-licensed 26 | * somewhat spec'd 27 | 28 | === Version 0.8.0 / 2010-02-05 29 | 30 | * bug fixes 31 | * fixed README documentation to properly reflect is_versioned 32 | * put core class extensions in a subdirectory of version, to avoid filename 33 | collisions with other gems 34 | 35 | * enhancements 36 | * added Version.current method (closes #2) 37 | 38 | === Version 0.7.1 / 2010-02-05 39 | 40 | * bug fixes 41 | * require version in rake task (closes #1) 42 | 43 | === Version 0.7.0 / 2010-02-05 44 | 45 | * enhancements 46 | * support for committing and tagging through git 47 | * see rdoc for Rake::VersionTask 48 | 49 | === Version 0.6.2 / 2010-02-05 50 | 51 | * bug fixes 52 | * fix critical bug in Class#is_versioned which prevented it from working at 53 | all 54 | 55 | === Version 0.6.1 / 2010-02-05 56 | 57 | * enhancements 58 | * renamed Versioned() to is_versioned 59 | 60 | === Version 0.6.0 / 2010-02-04 61 | 62 | * bug fixes 63 | * when autozeroing version components, use strings, not integers 64 | 65 | * enhancements 66 | * readme and history files 67 | * extended documentation 68 | * support for easy version-file management through rake 69 | * see rdoc for Rake::VersionTask 70 | * support auto-setting VERSION constant in classes 71 | * see rdoc for Class::Versioned() 72 | * eating own dogfood with Rake::VersionTask 73 | * make Version#to_hash exclude keys with nil values 74 | 75 | * todo for 1.0.0 76 | * full suite of specs 77 | * remove duplication in Class::Version() and Rake::VersionTask 78 | * get rid of gem task warnings 79 | 80 | === Version 0.5.0 / 2010-02-04 81 | 82 | * initial release 83 | 84 | * todo for 1.0.0 85 | * full suite of specs 86 | * version-bumping rake tasks 87 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2010-2010 Stephen Touset 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = Version 2 | 3 | * http://github.com/stouset/version 4 | * http://rdoc.info/gems/version/ 5 | * http://atlruby.org/stouset/posts/138-version-task 6 | 7 | == Description 8 | 9 | Version is a simple wrapper around the concept of version-numbering schemes. 10 | 11 | == Features 12 | 13 | * Rake::VersionTask provides tasks for simple version bumping 14 | * Version smartly handles several versioning schemes, abstracting the details 15 | 16 | == Examples 17 | 18 | === Screencast 19 | 20 | For a quick introduction, watch the 21 | screencast[http://blip.tv/file/get/Tkadom-ATLRUGSteveTousetPresentingVersion277.mov] 22 | of my presentation[http://atlruby.org/stouset/posts/138-version-task] at the 23 | {Atlanta Ruby Users' Group}[http://atlruby.org/]. 24 | 25 | === Rake Tasks 26 | 27 | Version comes with a Rake::VersionTask that lets you manage version numbering 28 | automatically. Place the following in a Rakefile: 29 | 30 | require 'rake/version_task' 31 | Rake::VersionTask.new 32 | 33 | You're all set up. 34 | 35 | $ rake version:create VERSION=0.1.0 # => 0.1.0 36 | 37 | Now `rake -T version` will tell you what all you can do. 38 | 39 | $ version [master *$%]$ rake -T version 40 | rake version # Print the current version number (0.1.0) 41 | rake version:bump # Bump to 0.1.1 42 | rake version:bump:major # Bump to 1.0.0 43 | rake version:bump:minor # Bump to 0.2.0 44 | rake version:bump:pre # Bump to 0.1.1a 45 | rake version:bump:pre:major # Bump to 1.0.0a 46 | rake version:bump:pre:minor # Bump to 0.2.0a 47 | rake version:bump:pre:revision # Bump to 0.1.1a 48 | rake version:bump:revision # Bump to 0.1.1 49 | rake version:create # Creates a version file with an optional VERSION parameter 50 | 51 | $ rake version # => 0.1.0 52 | $ rake version:bump # => 0.1.1 53 | $ rake version:bump:minor # => 0.2.0 54 | $ rake version:bump:revision # => 0.2.1 55 | $ rake version:bump:pre # => 0.2.2a 56 | $ rake version:bump # => 0.2.2 57 | $ rake version:bump:major # => 1.0.0 58 | $ rake version:bump:minor # => 1.1.0 59 | $ cat VERSION # => 1.1.0 60 | 61 | The VersionTask can automatically manage git tagging for 62 | you, too. 63 | 64 | Rake::VersionTask.new do |task| 65 | task.with_git_tag = true 66 | end 67 | 68 | And if you want the VersionTask to automatically emit updated gemspecs on 69 | version-bumps, use the +with_gemspec+ flag. 70 | 71 | spec = Gem::Specification.new do |s| 72 | ... 73 | end 74 | 75 | Rake::VersionTask.new do |task| 76 | task.with_gemspec = spec 77 | end 78 | 79 | Version also supports a .yml VERSION file. See the VersionTask rdoc for 80 | details. 81 | 82 | === Library Versioning 83 | 84 | Version lets you automatically keep an in-class VERSION constant in sync with 85 | the contents of the version file on disk. Version also provides a class-level 86 | +current+ method which lets you get the current version without setting a 87 | class-level constant. 88 | 89 | require 'version' 90 | 91 | Version.current # => 1.0.1 92 | 93 | class Foo 94 | is_versioned 95 | end 96 | 97 | Foo::VERSION # => 1.0.1 98 | 99 | The Version.current and Class::is_versioned methods both take a filename 100 | parameter if you use a different location for the VERSION file. See the 101 | Version.current rdoc for details. 102 | 103 | === Manipulation in Code 104 | 105 | All the above functionality is performed behind-the-scenes by the Version 106 | library. It's simple to use, but I'll be surprised if there's much point 107 | beyond doing the legwork for the Rake task and class versioning. 108 | 109 | v = "1.2.0".to_version 110 | v.to_s # => 1.2.0 111 | v.bump! # => 1.2.1 112 | v.bump!(:major) # => 2.0.0 113 | v.bump!(:minor, false, true) # => 2.1 114 | v.major = 3 # => 3.0 115 | v.to_a # => ['3', '0'] 116 | 117 | == Install 118 | 119 | [sudo] gem install version 120 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | $: << 'lib' 2 | 3 | require 'rake/version_task' 4 | 5 | require 'rubygems' 6 | require 'rubygems/package_task' 7 | require 'rdoc/task' 8 | require 'rspec/core/rake_task' 9 | 10 | spec = Gem::Specification.new do |s| 11 | s.name = 'version' 12 | s.version = Version.current or '0.0.0' 13 | s.summary = 'simple version-number encapsulation' 14 | 15 | s.author = 'Stephen Touset' 16 | s.email = 'stephen@touset.org' 17 | s.homepage = 'https://github.com/stouset/version' 18 | 19 | s.licenses = ['MIT'] 20 | 21 | s.files = Dir['[A-Z]*', 'lib/**/*.rb', 'spec/**/*'] 22 | 23 | s.extra_rdoc_files = Dir['*.rdoc'] 24 | s.rdoc_options = %w{ --main README.rdoc } 25 | 26 | s.add_development_dependency 'rake', '~> 12' 27 | s.add_development_dependency 'rspec', '~> 3' 28 | s.add_development_dependency 'rspec-its', '~> 1' 29 | end 30 | 31 | Gem::PackageTask.new(spec) do |gem| 32 | gem.need_tar = true 33 | end 34 | 35 | Rake::RDocTask.new do |doc| 36 | doc.title = "version #{Version.current}" 37 | doc.rdoc_dir = 'doc' 38 | doc.main = 'README.rdoc' 39 | doc.rdoc_files.include('*.rdoc') 40 | doc.rdoc_files.include('lib/**/*.rb') 41 | end 42 | 43 | RSpec::Core::RakeTask.new(:spec) do |task| 44 | task.pattern = 'spec/**/*_spec.rb' 45 | end 46 | 47 | Rake::VersionTask.new do |v| 48 | v.with_git_tag = true 49 | v.with_gemspec = spec 50 | end 51 | 52 | task :default => :spec 53 | -------------------------------------------------------------------------------- /TODO.rdoc: -------------------------------------------------------------------------------- 1 | * remove file parsing duplication between Version.current and 2 | Rake::VersionTask 3 | * get rid of gem task warnings 4 | * improved pre-bumping (version:bump:major:pre?) 5 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.1.1 2 | -------------------------------------------------------------------------------- /lib/rake/version_task.rb: -------------------------------------------------------------------------------- 1 | require 'version' 2 | 3 | require 'rake/tasklib' 4 | require 'pathname' 5 | 6 | class Rake::VersionTask < Rake::TaskLib 7 | attr_accessor :filename 8 | attr_writer :filetype 9 | 10 | # when true, commits version bumps automatically (default: autodetect) 11 | attr_accessor :with_git 12 | 13 | # when true, tags version bumps automatically (default: false) 14 | attr_accessor :with_git_tag 15 | 16 | # when true, commits version bumps automatically (default: autodetect) 17 | attr_accessor :with_hg 18 | 19 | # when true, tags version bumps automatically (default: false) 20 | attr_accessor :with_hg_tag 21 | 22 | # when true, commits version bumps automatically (default: autodetect) 23 | attr_accessor :with_svn 24 | 25 | # when true, tags version bumps automatically if the current svn URL 26 | # either ends in '/trunk' or '/branches/' by 27 | # copying the current svn URL to the '/tags/' 28 | # (default: false) 29 | attr_accessor :with_svn_tag 30 | 31 | # when set with a Gem::Specification, automatically emits an updated 32 | # gemspec on version bumps 33 | attr_accessor :with_gemspec 34 | 35 | # when set allows to override commit message 36 | attr_accessor :with_commit_message 37 | 38 | # 39 | # Creates a new VersionTask with the given +filename+. Attempts to 40 | # autodetect the +filetype+ and whether or not git or hg is present. 41 | # 42 | def initialize(filename = 'VERSION') 43 | self.filename = filename 44 | self.with_git = File.exist?('.git') 45 | self.with_hg = File.exist?('.hg') 46 | self.with_svn = File.exist?('.svn') 47 | 48 | yield(self) if block_given? 49 | 50 | self.define 51 | end 52 | 53 | # 54 | # The +filetype+ of the file to be generated. Is determined automatically 55 | # if not set. 56 | # 57 | def filetype 58 | @filetype || self.path.extname[1..-1] 59 | end 60 | 61 | def gemspec 62 | Pathname("#{with_gemspec.name}.gemspec") if with_gemspec 63 | end 64 | 65 | protected 66 | 67 | # 68 | # The path for the +filename+. 69 | # 70 | def path 71 | Pathname.new(self.filename) 72 | end 73 | 74 | # 75 | # Defines the rake tasks. 76 | # 77 | def define 78 | fail 'Filename required' if self.filename.nil? 79 | 80 | file filename 81 | 82 | desc "Print the current version number (#{read})" 83 | task(:version => filename) { puts read } 84 | 85 | namespace :version do 86 | desc 'Creates a version file with an optional VERSION parameter' 87 | task(:create) do 88 | version = (ENV['VERSION'] || '0.0.0').to_version 89 | puts write(version) 90 | end 91 | 92 | desc "Bump to #{read.bump!}" 93 | task(:bump => filename) { puts write(read.bump!) } 94 | 95 | namespace :bump do 96 | desc "Bump to #{read.bump!(:major)}" 97 | task(:major => filename) { puts write(read.bump!(:major)) } 98 | 99 | desc "Bump to #{read.bump!(:minor)}" 100 | task(:minor => filename) { puts write(read.bump!(:minor)) } 101 | 102 | desc "Bump to #{read.bump!(:revision)}" 103 | task(:revision => filename) { puts write(read.bump!(:revision)) } 104 | 105 | desc "Bump to #{read.bump!(:pre)}" 106 | task(:pre => filename) { puts write(read.bump!(:pre)) } 107 | 108 | namespace :pre do 109 | desc "Bump to #{read.bump!(:major, true)}" 110 | task(:major => filename) { puts write(read.bump!(:major, true)) } 111 | 112 | desc "Bump to #{read.bump!(:minor, true)}" 113 | task(:minor => filename) { puts write(read.bump!(:minor, true)) } 114 | 115 | desc "Bump to #{read.bump!(:revision, true)}" 116 | task(:revision => filename) { puts write(read.bump!(:revision, true)) } 117 | end 118 | end 119 | end 120 | end 121 | 122 | private 123 | 124 | # 125 | # Returns the Version contained in the file at +filename+. 126 | # 127 | def read 128 | contents = path.read rescue '0.0.0' 129 | 130 | case filetype.to_s 131 | when '' then contents.chomp.to_version 132 | when 'yml' then YAML::load(contents).to_version 133 | end 134 | end 135 | 136 | def commit_message(version) 137 | self.with_commit_message || "Version bump to #{version}" 138 | end 139 | # 140 | # Writes out +version+ to the file at +filename+ with the correct format. 141 | # 142 | def write(version) 143 | return if version == read 144 | 145 | path.open('w') do |io| 146 | io << case filetype.to_s 147 | when '' then version.to_s + "\n" 148 | when 'yml' then version.to_yaml 149 | end 150 | end 151 | 152 | if self.with_gemspec 153 | with_gemspec.version = version 154 | gemspec.open('w') {|io| io << with_gemspec.to_ruby } 155 | end 156 | 157 | if self.with_git 158 | `git add #{self.filename}` 159 | `git add #{self.gemspec}` if self.with_gemspec 160 | `git commit -m "#{commit_message(version)}"` 161 | `git tag #{version}` if self.with_git_tag 162 | end 163 | 164 | if self.with_hg 165 | `hg add #{self.filename}` unless `hg status -u #{self.filename}`.empty? 166 | `hg add #{self.gemspec}` if (self.with_gemspec && !`hg status -u #{self.gemspec}`.empty?) 167 | `hg commit #{self.filename} #{self.with_gemspec ? self.gemspec : ''} -m "#{commit_message(version)}"` 168 | `hg tag #{version}` if self.with_hg_tag 169 | end 170 | 171 | if self.with_svn 172 | `svn commit #{self.filename} #{self.with_gemspec ? self.gemspec : ''} -m "#{commit_message(version)}"` 173 | 174 | # This only attempts to make 'standard' tags. That is, if the 175 | # current svn URL ends in 'trunk' or 'branches/', then 176 | # it will be copied to 'tags/' 177 | if self.with_svn_tag 178 | url = nil 179 | `svn info`.each_line do |line| 180 | if line =~ /^URL:\s+(.*)$/ 181 | url = $1 182 | break 183 | end 184 | end 185 | 186 | if url && url =~ /^(.*)\/(trunk|branches\/[\w]+)$/ 187 | base = $1 188 | tag_url = "#{base}/tags/#{version}" 189 | `svn copy #{url} #{tag_url} -m "Tag #{version}"` 190 | end 191 | end 192 | end 193 | 194 | version 195 | end 196 | end 197 | -------------------------------------------------------------------------------- /lib/version.rb: -------------------------------------------------------------------------------- 1 | require 'version/ext/array' 2 | require 'version/ext/module' 3 | require 'version/ext/hash' 4 | require 'version/ext/string' 5 | 6 | require 'pathname' 7 | 8 | # 9 | # Encodes version-numbering logic into a convenient class. 10 | # 11 | class Version 12 | include Comparable 13 | 14 | autoload :Component, 'version/component' 15 | 16 | # 17 | # Searches through the parent directories of the calling method and looks 18 | # for a VERSION or VERSION.yml file to parse out the current version. Pass 19 | # 20 | # Pass a filename to +path+ to override autodetection, or pass a directory 21 | # name as +path+ to autodetect within a given directory 22 | # 23 | def self.current(path = nil) 24 | # if path is nil, detect automatically; if path is a directory, detect 25 | # automatically in the directory; if path is a filename, use it directly 26 | path = path ? Pathname.new(path) : self.version_file(caller.first) 27 | path = self.version_file(path) unless path.nil? or path.file? 28 | 29 | return nil unless path 30 | 31 | case path.extname 32 | when '' then path.read.strip.to_version 33 | when '.yml' then YAML::load(path.read).to_version 34 | end 35 | end 36 | 37 | # 38 | # Attempts to detect the version file for the passed +filename+. Looks up 39 | # the directory hierarchy for a file named VERSION or VERSION.yml. Returns 40 | # a Pathname for the file if found, otherwise nil. 41 | # 42 | def self.version_file(filename) 43 | Pathname(filename).dirname.expand_path.ascend do |d| 44 | break d.join('VERSION') if d.join('VERSION').file? 45 | break d.join('VERSION.yml') if d.join('VERSION.yml').file? 46 | end 47 | end 48 | 49 | # 50 | # Creates a new version number, with a +major+ version number, +minor+ 51 | # revision number, +revision+ number, and optionally more (unnamed) 52 | # version components. 53 | # 54 | def initialize(major, minor = 0, revision = nil, *rest) 55 | self.components = [ major, minor, revision, *rest ] 56 | end 57 | 58 | # 59 | # For +major+, +minor+, and +revision+, make a helper method that gets and 60 | # sets each based on accessing indexes. 61 | #-- 62 | # TODO: make these rdoc-capable 63 | #++ 64 | # 65 | [ :major, :minor, :revision ].to_enum.each.with_index do |component, i| 66 | define_method(:"#{component}") { self.components[i] ? self.components[i].to_s : nil } 67 | define_method(:"#{component}=") {|v| self[i] = v } 68 | end 69 | 70 | # 71 | # Set the component of the Version at +index+ to +value+. Zeroes out any 72 | # trailing components. 73 | # 74 | # If +index+ is greater than the length of the version number, pads the 75 | # version number with zeroes until +index+. 76 | # 77 | def []=(index, value) 78 | return self.resize!(index) if value.nil? || value.to_s.empty? 79 | return self[self.length + index] = value if index < 0 80 | 81 | length = self.length - index 82 | zeroes = Array.new length.abs, Version::Component.new('0') 83 | value = Version::Component.new(value.to_s) 84 | 85 | if length >= 0 86 | self.components[index, length] = zeroes 87 | self.components[index] = value 88 | else 89 | self.components += zeroes 90 | self.components << value 91 | end 92 | end 93 | 94 | def prerelease? 95 | self.components.any? {|c| c.prerelease? } 96 | end 97 | 98 | # 99 | # Resizes the Version to +length+, removing any trailing components. Is a 100 | # no-op if +length+ is greater than its current length. 101 | # 102 | def resize!(length) 103 | self.components = self.components.take(length) 104 | self 105 | end 106 | 107 | # 108 | # Bumps the version number and replaces the current object. Pass 109 | # +component+ to bump a component other than the least-significant 110 | # part. Set +pre+ to true if you want to bump the component to a 111 | # prerelease version. Set +trim+ to true if you want the version to 112 | # be resized to only large enough to contain the component set. 113 | # 114 | # "1.0.4a".bump! # => '1.0.4' 115 | # "1.0.4a".bump!(:pre) # => '1.0.4b' 116 | # "1.0.4a".bump!(:minor, false, true) # => '1.1' 117 | # "1.0.4a".bump!(:minor, true, true) # => '1.1a 118 | # "1.0.4a".bump!(:minor, true, false) # => '1.1.0a' 119 | # 120 | def bump!(component = -1, pre = false, trim = false) 121 | case component 122 | when :major then self.bump!(0, pre, trim) 123 | when :minor then self.bump!(1, pre, trim) 124 | when :revision then self.bump!(2, pre, trim) 125 | when :pre then self.bump!(-1, true, trim) 126 | else 127 | # resize to match the new length, if applicable 128 | self.resize!(component + 1) if (trim or component >= self.length) 129 | 130 | # mark all but the changed bit as non-prerelease 131 | self[0...component].each(&:unprerelease!) 132 | 133 | # I don't even understand this part any more; god help you 134 | self[component] = self[component].next if pre and self.prerelease? and component == self.length - 1 135 | self[component] = self[component].next unless pre and self.prerelease? and component == -1 136 | self[-1] = self[-1].next(true) if pre 137 | self 138 | end 139 | end 140 | 141 | # 142 | # Bumps the version number. 143 | # 144 | def bump(component = -1, pre = false, trim = false) 145 | self.dup.bump!(component, pre, trim) 146 | end 147 | 148 | # 149 | # Returns the current length of the version number. 150 | # 151 | def length 152 | self.components.length 153 | end 154 | 155 | # 156 | # Compares a Version against any +other+ object that responds to 157 | # +to_version+. 158 | # 159 | def <=>(other) 160 | self.components <=> other.to_version.components 161 | end 162 | 163 | # 164 | # Converts the version number into an array of its components. 165 | # 166 | def to_a 167 | self.components.map {|c| c.to_s } 168 | end 169 | 170 | # 171 | # Converts the version number into a hash of its components. 172 | # 173 | def to_hash 174 | { :major => self.major, 175 | :minor => self.minor, 176 | :revision => self.revision, 177 | :rest => self.length > 3 ? self.to_a.drop(3) : nil }. 178 | delete_if {|k,v| v.nil? } 179 | end 180 | 181 | # 182 | # The canonical representation of a version number. 183 | # 184 | def to_s 185 | self.to_a.join('.') 186 | end 187 | 188 | # 189 | # Returns +self+. 190 | # 191 | def to_version 192 | self 193 | end 194 | 195 | # 196 | # Returns a YAML representation of the version number. 197 | # 198 | def to_yaml 199 | YAML::dump(self.to_hash) 200 | end 201 | 202 | # 203 | # Returns a human-friendly version format. 204 | # 205 | def inspect 206 | self.to_s.inspect 207 | end 208 | 209 | protected 210 | 211 | # 212 | # Retrieves the component of the Version at +index+. 213 | # 214 | def [](index) 215 | self.components[index] || Component.new('0') 216 | end 217 | 218 | def components 219 | @components ||= [] 220 | end 221 | 222 | def components=(components) 223 | components.each_with_index {|c, i| self[i] = c } 224 | end 225 | end 226 | 227 | class Version 228 | is_versioned 229 | end 230 | -------------------------------------------------------------------------------- /lib/version/component.rb: -------------------------------------------------------------------------------- 1 | require 'version' 2 | 3 | class Version::Component 4 | attr_accessor :digits 5 | attr_accessor :letter 6 | 7 | # 8 | # Creates a single Component of a version, consisting of digits and 9 | # possibly a letter. For example, +1+, +3a+, +12+, or +0+. 10 | # 11 | def initialize(component) 12 | parts = component.split /(?=\D)/ 13 | 14 | self.digits = parts[0].to_i 15 | self.letter = parts[1].to_s.strip 16 | end 17 | 18 | def initialize_copy(other) 19 | self.digits = other.digits 20 | self.letter = other.letter.dup 21 | end 22 | 23 | def prerelease? 24 | not self.letter.empty? 25 | end 26 | 27 | def unprerelease! 28 | self.next! if self.prerelease? 29 | end 30 | 31 | def next(pre = false) 32 | self.dup.next!(pre) 33 | end 34 | 35 | def next!(pre = false) 36 | case 37 | when ( pre and self.prerelease?) then self.letter.next! 38 | when ( pre and not self.prerelease?) then self.letter = 'a' 39 | when (not pre and self.prerelease?) then self.letter = '' 40 | when (not pre and not self.prerelease?) then self.digits = self.digits.next 41 | end 42 | 43 | self 44 | end 45 | 46 | def <=>(other) 47 | self.to_sortable_a <=> other.to_sortable_a 48 | end 49 | 50 | def to_sortable_a 51 | [ self.digits, self.prerelease? ? 0 : 1, self.letter ] 52 | end 53 | 54 | def to_a 55 | [ self.digits, self.letter ] 56 | end 57 | 58 | def to_i 59 | self.digits 60 | end 61 | 62 | def to_s 63 | self.to_a.join 64 | end 65 | 66 | def inspect 67 | self.to_s.inspect 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/version/ext/array.rb: -------------------------------------------------------------------------------- 1 | require 'version' 2 | 3 | class Array 4 | # 5 | # Converts the Array into a version number. 6 | # 7 | def to_version 8 | Version.new *self 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/version/ext/hash.rb: -------------------------------------------------------------------------------- 1 | require 'version' 2 | 3 | class Hash 4 | # 5 | # Converts the Hash into a version number. 6 | # 7 | def to_version 8 | Version.new *self.values_at(:major, :minor, :revision, :rest) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/version/ext/module.rb: -------------------------------------------------------------------------------- 1 | require 'version' 2 | 3 | class Module 4 | # 5 | # Automagically sets a VERSION constant in the current module according to 6 | # the results of Version.current. 7 | # 8 | def is_versioned 9 | const_set :VERSION, Version.current(File.dirname(caller.first)) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/version/ext/string.rb: -------------------------------------------------------------------------------- 1 | require 'version' 2 | 3 | class String 4 | # 5 | # Converts the String into a version number. 6 | # 7 | def to_version 8 | Version.new *self.split(%r{\.}) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/spec.opts: -------------------------------------------------------------------------------- 1 | --color -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'version' 2 | # This file was generated by the `rspec --init` command. Conventionally, all 3 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 4 | # The generated `.rspec` file contains `--require spec_helper` which will cause 5 | # this file to always be loaded, without a need to explicitly require it in any 6 | # files. 7 | # 8 | # Given that it is always loaded, you are encouraged to keep this file as 9 | # light-weight as possible. Requiring heavyweight dependencies from this file 10 | # will add to the boot time of your test suite on EVERY test run, even for an 11 | # individual file that may not need all of that loaded. Instead, consider making 12 | # a separate helper file that requires the additional dependencies and performs 13 | # the additional setup, and require it from the spec files that actually need 14 | # it. 15 | # 16 | # The `.rspec` file also contains a few flags that are not defaults but that 17 | # users commonly want. 18 | # 19 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 20 | RSpec.configure do |config| 21 | # rspec-expectations config goes here. You can use an alternate 22 | # assertion/expectation library such as wrong or the stdlib/minitest 23 | # assertions if you prefer. 24 | config.expect_with :rspec do |expectations| 25 | # This option will default to `true` in RSpec 4. It makes the `description` 26 | # and `failure_message` of custom matchers include text for helper methods 27 | # defined using `chain`, e.g.: 28 | # be_bigger_than(2).and_smaller_than(4).description 29 | # # => "be bigger than 2 and smaller than 4" 30 | # ...rather than: 31 | # # => "be bigger than 2" 32 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 33 | end 34 | 35 | # rspec-mocks config goes here. You can use an alternate test double 36 | # library (such as bogus or mocha) by changing the `mock_with` option here. 37 | config.mock_with :rspec do |mocks| 38 | # Prevents you from mocking or stubbing a method that does not exist on 39 | # a real object. This is generally recommended, and will default to 40 | # `true` in RSpec 4. 41 | mocks.verify_partial_doubles = true 42 | end 43 | 44 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will 45 | # have no way to turn it off -- the option exists only for backwards 46 | # compatibility in RSpec 3). It causes shared context metadata to be 47 | # inherited by the metadata hash of host groups and examples, rather than 48 | # triggering implicit auto-inclusion in groups with matching metadata. 49 | config.shared_context_metadata_behavior = :apply_to_host_groups 50 | 51 | # The settings below are suggested to provide a good initial experience 52 | # with RSpec, but feel free to customize to your heart's content. 53 | =begin 54 | # This allows you to limit a spec run to individual examples or groups 55 | # you care about by tagging them with `:focus` metadata. When nothing 56 | # is tagged with `:focus`, all examples get run. RSpec also provides 57 | # aliases for `it`, `describe`, and `context` that include `:focus` 58 | # metadata: `fit`, `fdescribe` and `fcontext`, respectively. 59 | config.filter_run_when_matching :focus 60 | 61 | # Allows RSpec to persist some state between runs in order to support 62 | # the `--only-failures` and `--next-failure` CLI options. We recommend 63 | # you configure your source control system to ignore this file. 64 | config.example_status_persistence_file_path = "spec/examples.txt" 65 | 66 | # Limits the available syntax to the non-monkey patched syntax that is 67 | # recommended. For more details, see: 68 | # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ 69 | # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 70 | # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode 71 | config.disable_monkey_patching! 72 | 73 | # This setting enables warnings. It's recommended, but in some cases may 74 | # be too noisy due to issues in dependencies. 75 | config.warnings = true 76 | 77 | # Many RSpec users commonly either run the entire suite or an individual 78 | # file, and it's useful to allow more verbose output when running an 79 | # individual spec file. 80 | if config.files_to_run.one? 81 | # Use the documentation formatter for detailed output, 82 | # unless a formatter has already been configured 83 | # (e.g. via a command-line flag). 84 | config.default_formatter = 'doc' 85 | end 86 | 87 | # Print the 10 slowest examples and example groups at the 88 | # end of the spec run, to help surface which specs are running 89 | # particularly slow. 90 | config.profile_examples = 10 91 | 92 | # Run specs in random order to surface order dependencies. If you find an 93 | # order dependency and want to debug it, you can fix the order by providing 94 | # the seed, which is printed after each run. 95 | # --seed 1234 96 | config.order = :random 97 | 98 | # Seed global randomization in this process using the `--seed` CLI option. 99 | # Setting this allows you to use `--seed` to deterministically reproduce 100 | # test failures related to randomization by passing the same `--seed` value 101 | # as the one that triggered the failure. 102 | Kernel.srand config.seed 103 | =end 104 | end 105 | -------------------------------------------------------------------------------- /spec/version_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/its' 2 | 3 | module ImplicitVersion 4 | def method_missing(name, *args, &block) 5 | super unless args.empty? 6 | super unless block.nil? 7 | super unless name.to_s =~ /^v[\d\w_]+$/ 8 | 9 | name.to_s.gsub(/^v/, '').gsub(/_/, '.').to_version 10 | end 11 | end 12 | 13 | describe Version do 14 | include ImplicitVersion 15 | 16 | subject { v2_9 } 17 | 18 | its(:major) { is_expected.to eq('2') } 19 | its(:minor) { is_expected.to eq('9') } 20 | its(:revision) { is_expected.to be_nil } 21 | its(:prerelease?) { is_expected.to be_falsey } 22 | 23 | it 'is_expected.to bump to 2.10' do 24 | expect(subject.bump!).to eq(v2_10) 25 | end 26 | 27 | it 'is_expected.to major-bump to 3.0' do 28 | expect(subject.bump!(:major)).to eq(v3_0) 29 | end 30 | 31 | it 'is_expected.to minor-bump to 2.10' do 32 | expect(subject.bump!(:minor)).to eq(v2_10) 33 | end 34 | 35 | it 'is_expected.to revision-bump to 2.9.1' do 36 | expect(subject.bump!(:revision)).to eq(v2_9_1) 37 | end 38 | 39 | it 'is_expected.to prerelease-bump to 2.10a' do 40 | expect(subject.bump!(:pre)).to eq(v2_10a) 41 | end 42 | 43 | it 'is_expected.to prerelease-bump major to 3_0a' do 44 | expect(subject.bump!(:major, true)).to eq(v3_0a) 45 | end 46 | 47 | it 'is_expected.to prerelease-bump minor to 2.10a' do 48 | expect(subject.bump!(:minor, true)).to eq(v2_10a) 49 | end 50 | 51 | it 'is_expected.to prerelease-bump revision to 2.9.1a' do 52 | expect(subject.bump!(:revision, true)).to eq(v2_9_1a) 53 | end 54 | end 55 | 56 | describe Version do 57 | include ImplicitVersion 58 | 59 | subject { v0_10_0 } 60 | 61 | its(:major) { is_expected.to eq('0') } 62 | its(:minor) { is_expected.to eq('10') } 63 | its(:revision) { is_expected.to eq('0') } 64 | its(:prerelease?) { is_expected.to be_falsey } 65 | 66 | it 'is_expected.to bump to 0.10.1' do 67 | expect(subject.bump!).to eq(v0_10_1) 68 | end 69 | 70 | it 'is_expected.to major-bump to 1.0.0' do 71 | expect(subject.bump!(:major)).to eq(v1_0_0) 72 | end 73 | 74 | it 'is_expected.to minor-bump to 0.11.0' do 75 | expect(subject.bump!(:minor)).to eq(v0_11_0) 76 | end 77 | 78 | it 'is_expected.to revision-bump to 0.10.1' do 79 | expect(subject.bump!(:revision)).to eq(v0_10_1) 80 | end 81 | 82 | it 'is_expected.to prerelease-bump to 0.10.1a' do 83 | expect(subject.bump!(:pre)).to eq(v0_10_1a) 84 | end 85 | 86 | it 'is_expected.to prerelease-bump major to 1.0.0a' do 87 | expect(subject.bump!(:major, true)).to eq(v1_0_0a) 88 | end 89 | 90 | it 'is_expected.to prerelease-bump minor to 0.11.0a' do 91 | expect(subject.bump!(:minor, true)).to eq(v0_11_0a) 92 | end 93 | 94 | it 'is_expected.to prerelease-bump revision to 0.10.1a' do 95 | expect(subject.bump!(:revision, true)).to eq(v0_10_1a) 96 | end 97 | end 98 | 99 | 100 | describe Version, 'with a prerelease revision' do 101 | include ImplicitVersion 102 | 103 | subject { v1_6_3a } 104 | 105 | its(:major) { is_expected.to eq('1') } 106 | its(:minor) { is_expected.to eq('6') } 107 | its(:revision) { is_expected.to eq('3a') } 108 | its(:prerelease?) { is_expected.to be_truthy } 109 | 110 | it 'is_expected.to bump to 1.6.3' do 111 | expect(subject.bump!).to eq(v1_6_3) 112 | end 113 | 114 | it 'is_expected.to major-bump to 2.0.0' do 115 | expect(subject.bump!(:major)).to eq(v2_0_0) 116 | end 117 | 118 | it 'is_expected.to minor-bump to 1.7.0' do 119 | expect(subject.bump!(:minor)).to eq(v1_7_0) 120 | end 121 | 122 | it 'is_expected.to revision-bump to 1.6.3' do 123 | expect(subject.bump!(:revision)).to eq(v1_6_3) 124 | end 125 | 126 | it 'is_expected.to prerelease-bump to 1.6.3b' do 127 | expect(subject.bump!(:pre)).to eq(v1_6_3b) 128 | end 129 | 130 | it 'is_expected.to prerelease-bump major to 2.0.0a' do 131 | expect(subject.bump!(:major, true)).to eq(v2_0_0a) 132 | end 133 | 134 | it 'is_expected.to prerelease-bump minor to 1.7.0a' do 135 | expect(subject.bump!(:minor, true)).to eq(v1_7_0a) 136 | end 137 | 138 | it 'is_expected.to prerelease-bump revision to 1.6.4a' do 139 | expect(subject.bump!(:revision, true)).to eq(v1_6_4a) 140 | end 141 | end 142 | 143 | describe Version, 'with a prerelease minor version' do 144 | include ImplicitVersion 145 | 146 | subject { v1_6a } 147 | 148 | its(:major) { is_expected.to eq('1') } 149 | its(:minor) { is_expected.to eq('6a') } 150 | its(:revision) { is_expected.to eq(nil) } 151 | its(:prerelease?) { is_expected.to be_truthy } 152 | 153 | it 'is_expected.to bump to 1.6' do 154 | expect(subject.bump!).to eq(v1_6) 155 | end 156 | 157 | it 'is_expected.to major-bump to 2.0' do 158 | expect(subject.bump!(:major)).to eq(v2_0) 159 | end 160 | 161 | it 'is_expected.to minor-bump to 1.6' do 162 | expect(subject.bump!(:minor)).to eq(v1_6) 163 | end 164 | 165 | it 'is_expected.to revision-bump to 1.6.1' do 166 | expect(subject.bump!(:revision)).to eq(v1_6_1) 167 | end 168 | 169 | it 'is_expected.to bump to 1.6b' do 170 | expect(subject.bump!(:pre)).to eq(v1_6b) 171 | end 172 | 173 | it 'is_expected.to prerelease-bump major to 2.0a' do 174 | expect(subject.bump!(:major, true)).to eq(v2_0a) 175 | end 176 | 177 | it 'is_expected.to prerelease-bump minor to 1.7a' do 178 | expect(subject.bump!(:minor, true)).to eq(v1_7a) 179 | end 180 | 181 | it 'is_expected.to prerelease-bump revision to 1.6.1a' do 182 | expect(subject.bump!(:revision, true)).to eq(v1_6_1a) 183 | end 184 | end 185 | 186 | describe Version do 187 | include ImplicitVersion 188 | 189 | it 'is_expected.to preserve equality' do 190 | expect(v0_0).to eq(v0_0) 191 | expect(v0_1_1).to eq(v0_1_1) 192 | expect(v0_4_alpha).to eq(v0_4_alpha) 193 | expect(v1_0_2).to eq(v1_0_2) 194 | expect(v1_0_2b).to eq(v1_0_2b) 195 | expect(v1_01).to eq(v1_01) 196 | expect(v1_10).to eq(v1_10) 197 | expect(v2_0).to eq(v2_0) 198 | expect(va).to eq(vb) 199 | end 200 | 201 | it 'is_expected.to order correctly' do 202 | expect(v0_0).to be < v0_0_0_0_0_1 203 | expect(v0_0_0_1).to be < v1 204 | expect(v0_1a).to be < v0_1 205 | expect(v0_01).to be < v0_10 206 | expect(v0_9).to be < v0_10 207 | end 208 | end 209 | -------------------------------------------------------------------------------- /version.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # stub: version 1.1.1 ruby lib 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "version".freeze 6 | s.version = "1.1.1" 7 | 8 | s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= 9 | s.require_paths = ["lib".freeze] 10 | s.authors = ["Stephen Touset".freeze] 11 | s.date = "2017-04-22" 12 | s.email = "stephen@touset.org".freeze 13 | s.extra_rdoc_files = ["History.rdoc".freeze, "README.rdoc".freeze, "TODO.rdoc".freeze] 14 | s.files = ["Gemfile".freeze, "Gemfile.lock".freeze, "History.rdoc".freeze, "License.txt".freeze, "README.rdoc".freeze, "Rakefile".freeze, "TODO.rdoc".freeze, "VERSION".freeze, "lib/rake/version_task.rb".freeze, "lib/version.rb".freeze, "lib/version/component.rb".freeze, "lib/version/ext/array.rb".freeze, "lib/version/ext/hash.rb".freeze, "lib/version/ext/module.rb".freeze, "lib/version/ext/string.rb".freeze, "spec/spec.opts".freeze, "spec/spec_helper.rb".freeze, "spec/version_spec.rb".freeze] 15 | s.homepage = "https://github.com/stouset/version".freeze 16 | s.licenses = ["MIT".freeze] 17 | s.rdoc_options = ["--main".freeze, "README.rdoc".freeze] 18 | s.rubygems_version = "2.6.10".freeze 19 | s.summary = "simple version-number encapsulation".freeze 20 | 21 | if s.respond_to? :specification_version then 22 | s.specification_version = 4 23 | 24 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 25 | s.add_development_dependency(%q.freeze, ["~> 12"]) 26 | s.add_development_dependency(%q.freeze, ["~> 3"]) 27 | s.add_development_dependency(%q.freeze, ["~> 1"]) 28 | else 29 | s.add_dependency(%q.freeze, ["~> 12"]) 30 | s.add_dependency(%q.freeze, ["~> 3"]) 31 | s.add_dependency(%q.freeze, ["~> 1"]) 32 | end 33 | else 34 | s.add_dependency(%q.freeze, ["~> 12"]) 35 | s.add_dependency(%q.freeze, ["~> 3"]) 36 | s.add_dependency(%q.freeze, ["~> 1"]) 37 | end 38 | end 39 | --------------------------------------------------------------------------------