semverve 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 +7 -0
- data/.github/workflows/ruby.yml +37 -0
- data/.gitignore +3 -0
- data/.ruby-version +1 -0
- data/CONTRIBUTING.md +155 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +93 -0
- data/LICENSE +21 -0
- data/README.md +551 -0
- data/Rakefile +45 -0
- data/lib/semverve/configuration.rb +471 -0
- data/lib/semverve/docs_publisher/task.rb +166 -0
- data/lib/semverve/docs_publisher.rb +370 -0
- data/lib/semverve/error.rb +8 -0
- data/lib/semverve/file_list_resolver.rb +66 -0
- data/lib/semverve/formats/module_constants.rb +140 -0
- data/lib/semverve/formats/simple_string.rb +72 -0
- data/lib/semverve/formats.rb +27 -0
- data/lib/semverve/generator.rb +66 -0
- data/lib/semverve/presets.rb +126 -0
- data/lib/semverve/project_metadata.rb +153 -0
- data/lib/semverve/railtie.rb +16 -0
- data/lib/semverve/semantic_version.rb +116 -0
- data/lib/semverve/task.rb +409 -0
- data/lib/semverve/version.rb +50 -0
- data/lib/semverve/version_code_references.rb +244 -0
- data/lib/semverve/version_file.rb +142 -0
- data/lib/semverve/version_metadata.rb +358 -0
- data/lib/semverve/version_references.rb +427 -0
- data/lib/semverve.rb +30 -0
- data/semverve.gemspec +36 -0
- metadata +156 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
require_relative "error"
|
|
6
|
+
require_relative "formats"
|
|
7
|
+
require_relative "semantic_version"
|
|
8
|
+
|
|
9
|
+
module Semverve
|
|
10
|
+
##
|
|
11
|
+
# Generates new Ruby version files from Semverve configuration.
|
|
12
|
+
class Generator
|
|
13
|
+
##
|
|
14
|
+
# Default version used when +VERSION+ is not provided.
|
|
15
|
+
#
|
|
16
|
+
# @return [String]
|
|
17
|
+
DEFAULT_VERSION = "0.1.0"
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
# Initializes a version-file generator.
|
|
21
|
+
#
|
|
22
|
+
# @param [Semverve::ResolvedConfiguration] configuration
|
|
23
|
+
# @param [#fetch] env
|
|
24
|
+
#
|
|
25
|
+
# @return [Semverve::Generator]
|
|
26
|
+
def initialize(configuration, env = ENV)
|
|
27
|
+
@configuration = configuration
|
|
28
|
+
@env = env
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
##
|
|
32
|
+
# Generates the configured version file.
|
|
33
|
+
#
|
|
34
|
+
# @return [String]
|
|
35
|
+
def generate
|
|
36
|
+
version = SemanticVersion.parse(env.fetch("VERSION", DEFAULT_VERSION))
|
|
37
|
+
format = Formats.fetch(env.fetch("FORMAT", configuration.format))
|
|
38
|
+
path = configuration.absolute_version_file
|
|
39
|
+
|
|
40
|
+
if File.exist?(path) && !force?
|
|
41
|
+
raise Error, "Version file already exists at #{path}. Set FORCE=true to overwrite it."
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
45
|
+
File.write(path, format.generate(version, module_name: configuration.module_name))
|
|
46
|
+
|
|
47
|
+
path
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
##
|
|
53
|
+
# Resolved configuration used for generation.
|
|
54
|
+
#
|
|
55
|
+
# @return [Semverve::ResolvedConfiguration]
|
|
56
|
+
attr_reader :configuration, :env
|
|
57
|
+
|
|
58
|
+
##
|
|
59
|
+
# Whether generation should overwrite an existing version file.
|
|
60
|
+
#
|
|
61
|
+
# @return [Boolean]
|
|
62
|
+
def force?
|
|
63
|
+
env.fetch("FORCE", "false").match?(/\A(true|1|yes)\z/i)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "error"
|
|
4
|
+
|
|
5
|
+
module Semverve
|
|
6
|
+
##
|
|
7
|
+
# Framework-specific default configuration adapters.
|
|
8
|
+
module Presets
|
|
9
|
+
##
|
|
10
|
+
# Returns defaults for a configured preset.
|
|
11
|
+
#
|
|
12
|
+
# @param [Symbol, String, nil] name
|
|
13
|
+
# @param [Semverve::Configuration] configuration
|
|
14
|
+
#
|
|
15
|
+
# @return [Hash]
|
|
16
|
+
def self.defaults_for(name, configuration)
|
|
17
|
+
return {} unless name
|
|
18
|
+
|
|
19
|
+
fetch(name).defaults(configuration)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
##
|
|
23
|
+
# Returns a preset adapter.
|
|
24
|
+
#
|
|
25
|
+
# @param [Symbol, String] name
|
|
26
|
+
#
|
|
27
|
+
# @return [#name, #defaults]
|
|
28
|
+
def self.fetch(name)
|
|
29
|
+
case name.to_sym
|
|
30
|
+
when :rails
|
|
31
|
+
Rails.new
|
|
32
|
+
else
|
|
33
|
+
raise Error, "Unknown preset #{name.inspect}. Use :rails."
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# Rails application defaults.
|
|
39
|
+
class Rails
|
|
40
|
+
##
|
|
41
|
+
# Preset name.
|
|
42
|
+
#
|
|
43
|
+
# @return [Symbol]
|
|
44
|
+
def name
|
|
45
|
+
:rails
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# Rails-specific Semverve defaults.
|
|
50
|
+
#
|
|
51
|
+
# @param [Semverve::Configuration] _configuration
|
|
52
|
+
#
|
|
53
|
+
# @return [Hash]
|
|
54
|
+
def defaults(_configuration)
|
|
55
|
+
root = rails_root || Dir.pwd
|
|
56
|
+
|
|
57
|
+
{
|
|
58
|
+
format: :simple,
|
|
59
|
+
module_name: rails_module_name || project_module_name(root),
|
|
60
|
+
root: root,
|
|
61
|
+
version_file: File.join("config", "version.rb")
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
##
|
|
68
|
+
# Rails.root when Rails is available.
|
|
69
|
+
#
|
|
70
|
+
# @return [String, nil]
|
|
71
|
+
def rails_root
|
|
72
|
+
return unless rails_defined?
|
|
73
|
+
return unless ::Rails.respond_to?(:root)
|
|
74
|
+
return unless ::Rails.root
|
|
75
|
+
|
|
76
|
+
::Rails.root.to_s
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
##
|
|
80
|
+
# Application module name inferred from Rails.application.
|
|
81
|
+
#
|
|
82
|
+
# @return [String, nil]
|
|
83
|
+
def rails_module_name
|
|
84
|
+
return unless rails_defined?
|
|
85
|
+
return unless ::Rails.respond_to?(:application)
|
|
86
|
+
return unless ::Rails.application
|
|
87
|
+
|
|
88
|
+
application_class = ::Rails.application.class
|
|
89
|
+
return application_class.module_parent_name if application_class.respond_to?(:module_parent_name)
|
|
90
|
+
|
|
91
|
+
class_name = application_class.name
|
|
92
|
+
return unless class_name&.include?("::")
|
|
93
|
+
|
|
94
|
+
class_name.split("::").first
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
##
|
|
98
|
+
# Module name fallback based on the project directory.
|
|
99
|
+
#
|
|
100
|
+
# @param [String] root
|
|
101
|
+
#
|
|
102
|
+
# @return [String]
|
|
103
|
+
def project_module_name(root)
|
|
104
|
+
camelize(File.basename(File.expand_path(root)))
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
##
|
|
108
|
+
# Whether Rails is loaded.
|
|
109
|
+
#
|
|
110
|
+
# @return [Boolean]
|
|
111
|
+
def rails_defined?
|
|
112
|
+
Object.const_defined?(:Rails)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
##
|
|
116
|
+
# Converts a project directory into a Ruby module name.
|
|
117
|
+
#
|
|
118
|
+
# @param [String] value
|
|
119
|
+
#
|
|
120
|
+
# @return [String]
|
|
121
|
+
def camelize(value)
|
|
122
|
+
value.split(/[_-]/).map(&:capitalize).join
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "error"
|
|
4
|
+
|
|
5
|
+
module Semverve
|
|
6
|
+
##
|
|
7
|
+
# Infers gem metadata from explicit configuration or project files.
|
|
8
|
+
class ProjectMetadata
|
|
9
|
+
##
|
|
10
|
+
# Initializes project metadata inference.
|
|
11
|
+
#
|
|
12
|
+
# @param [Semverve::Configuration] configuration
|
|
13
|
+
#
|
|
14
|
+
# @return [Semverve::ProjectMetadata]
|
|
15
|
+
def initialize(configuration)
|
|
16
|
+
@configuration = configuration
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
# Gem name from explicit config, the version-file path, or the gemspec.
|
|
21
|
+
#
|
|
22
|
+
# @return [String]
|
|
23
|
+
def gem_name
|
|
24
|
+
explicit_gem_name || configured_version_file_gem_name || gemspec_name
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
# Ruby module name from explicit config or the resolved gem name.
|
|
29
|
+
#
|
|
30
|
+
# @return [String]
|
|
31
|
+
def module_name
|
|
32
|
+
explicit_module_name || configured_module_name || camelize(gem_name)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
##
|
|
36
|
+
# Version-file path from explicit config or the resolved gem name.
|
|
37
|
+
#
|
|
38
|
+
# @return [String]
|
|
39
|
+
def version_file
|
|
40
|
+
configured_version_file || File.join("lib", gem_name, "version.rb")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# Configuration used for metadata inference.
|
|
47
|
+
#
|
|
48
|
+
# @return [Semverve::Configuration]
|
|
49
|
+
attr_reader :configuration
|
|
50
|
+
|
|
51
|
+
##
|
|
52
|
+
# Explicitly configured gem name.
|
|
53
|
+
#
|
|
54
|
+
# @return [String, nil]
|
|
55
|
+
def explicit_gem_name
|
|
56
|
+
configuration.gem_name
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
##
|
|
60
|
+
# Configured gem name after applying presets.
|
|
61
|
+
#
|
|
62
|
+
# @return [String, nil]
|
|
63
|
+
def configured_gem_name
|
|
64
|
+
configuration.resolved_value(:gem_name)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
##
|
|
68
|
+
# Explicitly configured Ruby module name.
|
|
69
|
+
#
|
|
70
|
+
# @return [String, nil]
|
|
71
|
+
def explicit_module_name
|
|
72
|
+
configuration.module_name
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
##
|
|
76
|
+
# Configured Ruby module name after applying presets.
|
|
77
|
+
#
|
|
78
|
+
# @return [String, nil]
|
|
79
|
+
def configured_module_name
|
|
80
|
+
configuration.resolved_value(:module_name)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
##
|
|
84
|
+
# Configured version-file path after applying presets.
|
|
85
|
+
#
|
|
86
|
+
# @return [String, nil]
|
|
87
|
+
def configured_version_file
|
|
88
|
+
configuration.resolved_value(:version_file)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
##
|
|
92
|
+
# Gem name inferred from the parent directory of the configured version file.
|
|
93
|
+
#
|
|
94
|
+
# @return [String, nil]
|
|
95
|
+
def configured_version_file_gem_name
|
|
96
|
+
return configured_gem_name if configured_gem_name
|
|
97
|
+
return unless configured_version_file
|
|
98
|
+
|
|
99
|
+
File.basename(File.dirname(configured_version_file))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
##
|
|
103
|
+
# Gem name extracted from the single gemspec in the project root.
|
|
104
|
+
#
|
|
105
|
+
# @return [String]
|
|
106
|
+
def gemspec_name
|
|
107
|
+
gemspec_file.then { |file| extract_name(file) }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
##
|
|
111
|
+
# Single gemspec file in the configured project root.
|
|
112
|
+
#
|
|
113
|
+
# @return [String]
|
|
114
|
+
def gemspec_file
|
|
115
|
+
files = Dir.glob(File.join(configuration.expanded_root, "*.gemspec"))
|
|
116
|
+
|
|
117
|
+
return files.first if files.one?
|
|
118
|
+
|
|
119
|
+
if files.empty?
|
|
120
|
+
raise Error, "Could not infer gem name because no .gemspec file was found. " \
|
|
121
|
+
"Set config.gem_name or config.version_file."
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
raise Error, "Could not infer gem name because multiple .gemspec files were found. " \
|
|
125
|
+
"Set config.gem_name or config.version_file."
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
##
|
|
129
|
+
# Reads a gemspec and extracts its +spec.name+ assignment.
|
|
130
|
+
#
|
|
131
|
+
# @param [String] file
|
|
132
|
+
#
|
|
133
|
+
# @return [String]
|
|
134
|
+
def extract_name(file)
|
|
135
|
+
content = File.read(file)
|
|
136
|
+
match = content.match(/^\s*\w+\.name\s*=\s*["']([^"']+)["']/)
|
|
137
|
+
|
|
138
|
+
return match[1] if match
|
|
139
|
+
|
|
140
|
+
raise Error, "Could not infer gem name from #{file}. Set config.gem_name."
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
##
|
|
144
|
+
# Converts a snake-case or kebab-case gem name into a Ruby module name.
|
|
145
|
+
#
|
|
146
|
+
# @param [String] value
|
|
147
|
+
#
|
|
148
|
+
# @return [String]
|
|
149
|
+
def camelize(value)
|
|
150
|
+
value.split(/[_-]/).map(&:capitalize).join
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../semverve"
|
|
4
|
+
|
|
5
|
+
module Semverve
|
|
6
|
+
##
|
|
7
|
+
# Rails integration for installing Semverve's Rake tasks.
|
|
8
|
+
class Railtie < ::Rails::Railtie
|
|
9
|
+
rake_tasks do
|
|
10
|
+
Semverve.configuration.preset = :rails unless Semverve.configuration.preset
|
|
11
|
+
|
|
12
|
+
require_relative "task"
|
|
13
|
+
Semverve::Task.install
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "error"
|
|
4
|
+
|
|
5
|
+
module Semverve
|
|
6
|
+
##
|
|
7
|
+
# Value object for a MAJOR.MINOR.PATCH semantic version.
|
|
8
|
+
class SemanticVersion
|
|
9
|
+
include Comparable
|
|
10
|
+
|
|
11
|
+
##
|
|
12
|
+
# Regular expression for Semverve's supported version format.
|
|
13
|
+
#
|
|
14
|
+
# @return [Regexp]
|
|
15
|
+
PATTERN = /\A(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)\z/
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
# Major version number.
|
|
19
|
+
#
|
|
20
|
+
# @return [Integer]
|
|
21
|
+
attr_reader :major
|
|
22
|
+
|
|
23
|
+
##
|
|
24
|
+
# Minor version number.
|
|
25
|
+
#
|
|
26
|
+
# @return [Integer]
|
|
27
|
+
attr_reader :minor
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
# Patch version number.
|
|
31
|
+
#
|
|
32
|
+
# @return [Integer]
|
|
33
|
+
attr_reader :patch
|
|
34
|
+
|
|
35
|
+
##
|
|
36
|
+
# Parses a value into a semantic version.
|
|
37
|
+
#
|
|
38
|
+
# @param [#to_s] value
|
|
39
|
+
#
|
|
40
|
+
# @return [Semverve::SemanticVersion]
|
|
41
|
+
def self.parse(value)
|
|
42
|
+
match = value.to_s.match(PATTERN)
|
|
43
|
+
|
|
44
|
+
unless match
|
|
45
|
+
raise Error, "Expected a semantic version in MAJOR.MINOR.PATCH format, got #{value.inspect}."
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
new(
|
|
49
|
+
major: match[:major].to_i,
|
|
50
|
+
minor: match[:minor].to_i,
|
|
51
|
+
patch: match[:patch].to_i
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
##
|
|
56
|
+
# Initializes a semantic version.
|
|
57
|
+
#
|
|
58
|
+
# @param [Integer] major
|
|
59
|
+
# @param [Integer] minor
|
|
60
|
+
# @param [Integer] patch
|
|
61
|
+
#
|
|
62
|
+
# @return [Semverve::SemanticVersion]
|
|
63
|
+
def initialize(major:, minor:, patch:)
|
|
64
|
+
@major = major
|
|
65
|
+
@minor = minor
|
|
66
|
+
@patch = patch
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
##
|
|
70
|
+
# Returns a new semantic version with the requested level incremented.
|
|
71
|
+
#
|
|
72
|
+
# @param [Symbol, String] level
|
|
73
|
+
#
|
|
74
|
+
# @return [Semverve::SemanticVersion]
|
|
75
|
+
def increment(level)
|
|
76
|
+
case level.to_sym
|
|
77
|
+
when :major
|
|
78
|
+
self.class.new(major: major + 1, minor: 0, patch: 0)
|
|
79
|
+
when :minor
|
|
80
|
+
self.class.new(major: major, minor: minor + 1, patch: 0)
|
|
81
|
+
when :patch
|
|
82
|
+
self.class.new(major: major, minor: minor, patch: patch + 1)
|
|
83
|
+
else
|
|
84
|
+
raise Error, "Unknown version increment level: #{level.inspect}."
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
##
|
|
89
|
+
# Compares semantic versions by major, minor, then patch.
|
|
90
|
+
#
|
|
91
|
+
# @param [Semverve::SemanticVersion] other
|
|
92
|
+
#
|
|
93
|
+
# @return [Integer, nil]
|
|
94
|
+
def <=>(other)
|
|
95
|
+
return unless other.is_a?(self.class)
|
|
96
|
+
|
|
97
|
+
to_a <=> other.to_a
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
##
|
|
101
|
+
# Version as +MAJOR.MINOR.PATCH+.
|
|
102
|
+
#
|
|
103
|
+
# @return [String]
|
|
104
|
+
def to_s
|
|
105
|
+
to_a.join(".")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
##
|
|
109
|
+
# Version as +[major, minor, patch]+.
|
|
110
|
+
#
|
|
111
|
+
# @return [Array<Integer>]
|
|
112
|
+
def to_a
|
|
113
|
+
[major, minor, patch]
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|