├── .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 |
--------------------------------------------------------------------------------