nextver 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fa333b84952919e31e0f15823a5828df46d8559518279f1357ff9213f431dd32
4
+ data.tar.gz: 0752c983002b4fbd7c670fa620b832f2b9b4c0cb9d6ee212d7c105ebabcc3be1
5
+ SHA512:
6
+ metadata.gz: 0bc20baaef047cfaadfcab64296e628d8a887cfe392d478617c219f0781267691fa60cf2d4073d69cbc969586ce5b4d79bdbcd352448cd1884f07db79f69d1cb
7
+ data.tar.gz: dfabc6924f7c54152800603416d3bf13e5f50814abb9f18291ab01a554791d688f89add5a367161c7f57c962916bca0c2f2cd43bea299f7473378e962cd4c4a2
data/LICENSE.txt ADDED
@@ -0,0 +1,23 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) Chonlasith Jucksriporn and Contributors
4
+
5
+ All rights reserved.
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # nextver (Ruby gem)
2
+
3
+ Evaluate next version number with SemVer format based on [conventional commit](https://www.conventionalcommits.org/) messages. Ruby port of [chonla/nextver](https://github.com/chonla/nextver). Requires `git` on `PATH`.
4
+
5
+ ## Installation
6
+
7
+ ```ruby
8
+ # Gemfile
9
+ gem "nextver", require: false, group: :development
10
+ ```
11
+
12
+ or `gem install nextver`.
13
+
14
+ ## Usage
15
+
16
+ ```
17
+ bundle exec nextver [options...] [dir]
18
+
19
+ -d Debug mode, print considering steps.
20
+ -e Suppress trailing new line. Print only version out.
21
+ -n Version is not prefixed by v, for example, 1.0.0.
22
+ -t Show detected latest version.
23
+ -v Show version of nextver.
24
+ ```
25
+
26
+ From Ruby:
27
+
28
+ ```ruby
29
+ require "nextver"
30
+ Nextver::Repo.new(".").next_version # => "v1.1.0"
31
+ ```
32
+
33
+ ## Test
34
+
35
+ ```
36
+ ruby -Ilib test/nextver_test.rb
37
+ ```
38
+
39
+ ## License
40
+
41
+ [MIT](LICENSE.txt)
data/exe/nextver ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+ require "optparse"
3
+ require "nextver"
4
+
5
+ opts = {}
6
+ parser = OptionParser.new do |o|
7
+ o.banner = "Usage of nextver:\n\n nextver [options...] [dir]\n\nOptions:"
8
+ o.on("-d", "Debug mode, print considering steps.") { opts[:debug] = true }
9
+ o.on("-e", "Suppress trailing new line. Print only version out.") { opts[:no_newline] = true }
10
+ o.on("-n", "Version is not prefixed by v, for example, 1.0.0.") { opts[:no_prefix] = true }
11
+ o.on("-t", "Show detected latest version.") { opts[:latest] = true }
12
+ o.on("-v", "Show version of nextver.") { puts Nextver::VERSION; exit }
13
+ o.separator "\nFor more information, visit https://github.com/chonla/nextver."
14
+ end
15
+
16
+ begin
17
+ parser.parse!
18
+ log = ->(msg) { puts msg if opts[:debug] }
19
+ repo = Nextver::Repo.new(ARGV[0] || ".", prefixed: !opts[:no_prefix], log: log)
20
+ current = repo.current_version
21
+
22
+ if opts[:latest]
23
+ puts current
24
+ exit
25
+ end
26
+
27
+ log.call(current ? "Detected current version: #{current}" : "No version detected.")
28
+ nxt = repo.next_version(current)
29
+
30
+ if nxt == current
31
+ log.call("#{current} is the most recent version.")
32
+ else
33
+ log.call("Estimated next version: #{nxt}")
34
+ opts[:no_newline] ? print(nxt) : puts(nxt) unless opts[:debug]
35
+ end
36
+ rescue OptionParser::ParseError, Nextver::Error => e
37
+ puts "error: #{e.message}"
38
+ exit 1
39
+ end
@@ -0,0 +1,3 @@
1
+ module Nextver
2
+ VERSION = "0.1.0"
3
+ end
data/lib/nextver.rb ADDED
@@ -0,0 +1,83 @@
1
+ require "open3"
2
+ require "nextver/version"
3
+
4
+ module Nextver
5
+ class Error < StandardError; end
6
+
7
+ # Same rules as golang.org/x/mod/semver (major.minor / major-only shorthands allowed).
8
+ SEMVER = /\Av(0|[1-9]\d*)(?:\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?)?)?\z/
9
+ HEADER = /\A\w+(?:\([\w-]+\))?(!)?: .+/
10
+ BREAKING_FOOTER = /^BREAKING[ -]CHANGE(?:: | #)/
11
+
12
+ module_function
13
+
14
+ # Returns [major, minor, patch, prerelease] or nil when +tag+ isn't a version.
15
+ def parse(tag, prefixed: true)
16
+ m = SEMVER.match(prefixed ? tag : "v#{tag}")
17
+ m && [m[1].to_i, m[2].to_i, m[3].to_i, m[4]]
18
+ end
19
+
20
+ # ponytail: prerelease compared as plain string, not per-identifier as SemVer spec says.
21
+ def sort_key(parts)
22
+ parts[0, 3] + (parts[3] ? [0, parts[3]] : [1, ""])
23
+ end
24
+
25
+ # :major, :minor, :patch or nil for a single commit message.
26
+ def bump(message)
27
+ header = HEADER.match(message.strip) or return nil
28
+ return :major if header[1] || message.lines.drop(1).any? { |l| l =~ BREAKING_FOOTER }
29
+ { "feat" => :minor, "fix" => :patch }[message[/\A\w+/]]
30
+ end
31
+
32
+ class Repo
33
+ def initialize(dir = ".", prefixed: true, log: nil)
34
+ raise Error, "target path is not git repo path" unless File.directory?(File.join(dir, ".git"))
35
+ @dir = dir
36
+ @prefix = prefixed ? "v" : ""
37
+ @prefixed = prefixed
38
+ @log = log || ->(_) {}
39
+ end
40
+
41
+ def current_version
42
+ git("tag", "--list").split("\n")
43
+ .select { |t| Nextver.parse(t, prefixed: @prefixed) }
44
+ .max_by { |t| Nextver.sort_key(Nextver.parse(t, prefixed: @prefixed)) }
45
+ end
46
+
47
+ def next_version(current = current_version)
48
+ @log.call(@prefixed ? "Version is prefixed by v." : "Version is not prefixed by v.")
49
+ unless current
50
+ @log.call("Current version is missing. Start a new one at v1.0.0.")
51
+ return "#{@prefix}1.0.0"
52
+ end
53
+
54
+ @log.call("HEAD commit ID=#{git('rev-parse', 'HEAD').strip}")
55
+ @log.call("Latest tag commit ID=#{git('rev-parse', "#{current}^{commit}").strip}")
56
+ messages = git("log", "--format=%B%x00", "#{current}..HEAD").split("\0").map(&:strip).reject(&:empty?)
57
+ @log.call("#{messages.size} commit(s) since latest tag")
58
+
59
+ counts = messages.map { |m| Nextver.bump(m) }.compact.group_by(&:itself).transform_values(&:size)
60
+ @log.call("============\nCommit stats\n------------")
61
+ @log.call("Major change(s) = #{counts.fetch(:major, 0)}")
62
+ @log.call("Minor change(s) = #{counts.fetch(:minor, 0)}")
63
+ @log.call("Revision change(s) = #{counts.fetch(:patch, 0)}")
64
+ @log.call("============")
65
+
66
+ major, minor, patch = Nextver.parse(current, prefixed: @prefixed)
67
+ if counts[:major] then major += 1; minor = 0; patch = 0
68
+ elsif counts[:minor] then minor += 1; patch = 0
69
+ elsif counts[:patch] then patch += 1
70
+ else return current
71
+ end
72
+ "#{@prefix}#{major}.#{minor}.#{patch}"
73
+ end
74
+
75
+ private
76
+
77
+ def git(*args)
78
+ out, err, status = Open3.capture3("git", "-C", @dir, *args)
79
+ raise Error, err.strip unless status.success?
80
+ out
81
+ end
82
+ end
83
+ end
metadata ADDED
@@ -0,0 +1,49 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nextver
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Chonlasith Jucksriporn
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-09-24 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ - chonlasith@gmail.com
16
+ executables:
17
+ - nextver
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - LICENSE.txt
22
+ - README.md
23
+ - exe/nextver
24
+ - lib/nextver.rb
25
+ - lib/nextver/version.rb
26
+ homepage: https://github.com/chonla/nextver-gem
27
+ licenses:
28
+ - MIT
29
+ metadata: {}
30
+ post_install_message:
31
+ rdoc_options: []
32
+ require_paths:
33
+ - lib
34
+ required_ruby_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '2.6'
39
+ required_rubygems_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ requirements: []
45
+ rubygems_version: 3.0.3.1
46
+ signing_key:
47
+ specification_version: 4
48
+ summary: Evaluate next SemVer version from conventional commit messages.
49
+ test_files: []