qb 0.3.7 → 0.3.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/qb.rb +10 -4
- data/lib/qb/package.rb +102 -0
- data/lib/qb/package/gem.rb +153 -0
- data/lib/qb/package/version.rb +445 -422
- data/lib/qb/path.rb +219 -19
- data/lib/qb/repo/git.rb +90 -19
- data/lib/qb/role.rb +22 -4
- data/lib/qb/util/resource.rb +36 -0
- data/lib/qb/version.rb +1 -1
- data/qb.gemspec +1 -1
- data/roles/qb.qb_role/templates/qb.yml.j2 +3 -3
- data/roles/qb/gem/bin_stubs/defaults/main.yml +7 -0
- data/roles/qb/gem/bin_stubs/meta/main.yml +8 -0
- data/roles/qb/gem/bin_stubs/meta/qb.yml +76 -0
- data/roles/qb/gem/bin_stubs/tasks/main.yml +28 -0
- data/roles/qb/gem/bin_stubs/templates/console +25 -0
- data/roles/qb/gem/bin_stubs/templates/rake +3 -0
- data/roles/qb/gem/bin_stubs/templates/rspec +3 -0
- metadata +14 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fbc3b61137b1ac315d4a59b946216ffc49a698c5
|
4
|
+
data.tar.gz: 382fa7a1f0d99d90adfef8421811464811ecb855
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2399c2e4af9def73a5589e91a005aff11085663fe388d16010f2eb1684b36073faed4c778958056a4b63f84e107e810b43141723b01c18980e97805a301f8808
|
7
|
+
data.tar.gz: ef7ef9dd5b12cd9c8aab404a5ede52e5afdb642b1a4205c8f1a5fdc0ea45aa60aee2f9ef7c3bcbdb11177a37739804635aeb550a13b74da2a18ae455481449bf
|
data/lib/qb.rb
CHANGED
@@ -3,7 +3,7 @@ require 'nrser/extras'
|
|
3
3
|
require_relative './qb/errors'
|
4
4
|
require_relative './qb/version'
|
5
5
|
require_relative './qb/util'
|
6
|
-
require_relative './qb/
|
6
|
+
require_relative './qb/path'
|
7
7
|
|
8
8
|
module QB
|
9
9
|
ROOT = (Pathname.new(__FILE__).dirname + '..').expand_path
|
@@ -38,6 +38,12 @@ end
|
|
38
38
|
# needs QB::*_ROLES_DIR
|
39
39
|
require 'qb/role'
|
40
40
|
require 'qb/options'
|
41
|
-
|
42
|
-
|
43
|
-
|
41
|
+
require 'qb/repo'
|
42
|
+
require 'qb/cli'
|
43
|
+
|
44
|
+
require 'qb/ansible'
|
45
|
+
# Depreciated namespace:
|
46
|
+
require 'qb/ansible_module'
|
47
|
+
|
48
|
+
require 'qb/package'
|
49
|
+
|
data/lib/qb/package.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# Requirements
|
2
|
+
# =======================================================================
|
3
|
+
|
4
|
+
# Stdlib
|
5
|
+
# -----------------------------------------------------------------------
|
6
|
+
|
7
|
+
# Deps
|
8
|
+
# -----------------------------------------------------------------------
|
9
|
+
|
10
|
+
# Project / Package
|
11
|
+
# -----------------------------------------------------------------------
|
12
|
+
|
13
|
+
require 'qb/util/resource'
|
14
|
+
|
15
|
+
require_relative './package/version'
|
16
|
+
|
17
|
+
|
18
|
+
# Refinements
|
19
|
+
# =======================================================================
|
20
|
+
|
21
|
+
require 'nrser/refinements'
|
22
|
+
using NRSER
|
23
|
+
|
24
|
+
require 'nrser/refinements/types'
|
25
|
+
using NRSER::Types
|
26
|
+
|
27
|
+
|
28
|
+
# Declarations
|
29
|
+
# =======================================================================
|
30
|
+
|
31
|
+
module QB; end
|
32
|
+
|
33
|
+
|
34
|
+
# Definitions
|
35
|
+
# =======================================================================
|
36
|
+
|
37
|
+
# Common properties and methods of package resources, aimed at packages
|
38
|
+
# represented as directories in projects.
|
39
|
+
#
|
40
|
+
class QB::Package < QB::Util::Resource
|
41
|
+
|
42
|
+
# Constants
|
43
|
+
# ======================================================================
|
44
|
+
|
45
|
+
|
46
|
+
# Class Methods
|
47
|
+
# ======================================================================
|
48
|
+
|
49
|
+
|
50
|
+
# Properties
|
51
|
+
# ======================================================================
|
52
|
+
|
53
|
+
# @!attribute [r] ref_path
|
54
|
+
# User-provided path value used to construct the resource instance, if any.
|
55
|
+
#
|
56
|
+
# This may not be the same as a root path for the resource, such as with
|
57
|
+
# resource classes that can be constructed from any path *inside* the
|
58
|
+
# directory, like a {QB::Repo::Git}.
|
59
|
+
#
|
60
|
+
# @return [String | Pathname]
|
61
|
+
# If the resource instance was constructed based on a path argument.
|
62
|
+
#
|
63
|
+
# @return [nil]
|
64
|
+
# If the resource instance was *not* constructed based on a path
|
65
|
+
# argument.
|
66
|
+
#
|
67
|
+
prop :ref_path, type: t.maybe( t.dir_path )
|
68
|
+
|
69
|
+
|
70
|
+
# @!attribute [r] root_path
|
71
|
+
# Absolute path to the gem's root directory.
|
72
|
+
#
|
73
|
+
# @return [Pathname]
|
74
|
+
#
|
75
|
+
prop :root_path, type: t.dir_path
|
76
|
+
|
77
|
+
|
78
|
+
# @!attribute [r] version
|
79
|
+
# Version of the package.
|
80
|
+
#
|
81
|
+
# @return [QB::Package::Version]
|
82
|
+
#
|
83
|
+
prop :version, type: QB::Package::Version
|
84
|
+
|
85
|
+
|
86
|
+
# @!attribute [r] name
|
87
|
+
# The string name the package goes by.
|
88
|
+
#
|
89
|
+
# @return [String]
|
90
|
+
# Non-empty string.
|
91
|
+
#
|
92
|
+
prop :name, type: t.non_empty_str
|
93
|
+
|
94
|
+
|
95
|
+
end # class QB::Package
|
96
|
+
|
97
|
+
|
98
|
+
# Post-Processing
|
99
|
+
# =======================================================================
|
100
|
+
|
101
|
+
require_relative './package/gem'
|
102
|
+
|
@@ -0,0 +1,153 @@
|
|
1
|
+
# Requirements
|
2
|
+
# =======================================================================
|
3
|
+
|
4
|
+
# Stdlib
|
5
|
+
# -----------------------------------------------------------------------
|
6
|
+
|
7
|
+
# Deps
|
8
|
+
# -----------------------------------------------------------------------
|
9
|
+
|
10
|
+
# Project / Package
|
11
|
+
# -----------------------------------------------------------------------
|
12
|
+
require 'qb/package'
|
13
|
+
|
14
|
+
|
15
|
+
# Refinements
|
16
|
+
# =======================================================================
|
17
|
+
|
18
|
+
require 'nrser/refinements'
|
19
|
+
using NRSER
|
20
|
+
|
21
|
+
require 'nrser/refinements/types'
|
22
|
+
using NRSER::Types
|
23
|
+
|
24
|
+
|
25
|
+
# Definitions
|
26
|
+
# =======================================================================
|
27
|
+
|
28
|
+
# Package resource for a Ruby Gem.
|
29
|
+
#
|
30
|
+
class QB::Package::Gem < QB::Package
|
31
|
+
|
32
|
+
# Constants
|
33
|
+
# ======================================================================
|
34
|
+
|
35
|
+
|
36
|
+
# Eigenclass (Singleton Class)
|
37
|
+
# ========================================================================
|
38
|
+
#
|
39
|
+
class << self
|
40
|
+
|
41
|
+
# Find the only `*.gemspec` path in the `root_path` Gem directory.
|
42
|
+
#
|
43
|
+
# @param [String | Pathname] root_path
|
44
|
+
# Path to the gem's root directory.
|
45
|
+
#
|
46
|
+
def gemspec_path root_path
|
47
|
+
paths = Pathname.glob( root_path.to_pn / '*.gemspec' )
|
48
|
+
|
49
|
+
case paths.length
|
50
|
+
when 0
|
51
|
+
nil
|
52
|
+
when 1
|
53
|
+
paths[0]
|
54
|
+
else
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
end # #gemspec_path
|
58
|
+
|
59
|
+
|
60
|
+
# @todo Document from_path method.
|
61
|
+
#
|
62
|
+
# @param [String | Pathname] path
|
63
|
+
# Path to gem root directory.
|
64
|
+
#
|
65
|
+
# @return [QB::Package::Gem]
|
66
|
+
# If `path` is the root directory of a Ruby gem.
|
67
|
+
#
|
68
|
+
# @return [nil]
|
69
|
+
# If `path` is not the root directory of a Ruby gem.
|
70
|
+
#
|
71
|
+
# @raise [QB::FSStateError]
|
72
|
+
# If `path` is not a directory.
|
73
|
+
#
|
74
|
+
def from_root_path path
|
75
|
+
# Values we will use to construct the resource instance.
|
76
|
+
values = {}
|
77
|
+
|
78
|
+
# Whatever we were passes is the reference path
|
79
|
+
values[:ref_path] = path
|
80
|
+
|
81
|
+
# Cast to {Pathname} if it's not already and expand it to create the
|
82
|
+
# root path
|
83
|
+
values[:root_path] = path.to_pn.expand_path
|
84
|
+
|
85
|
+
# Check that we're working with a directory, returning `nil` if we're not
|
86
|
+
return nil unless values[:root_path].directory?
|
87
|
+
|
88
|
+
# Get the path to the (single) Gemspec file.
|
89
|
+
values[:gemspec_path] = self.gemspec_path values[:root_path]
|
90
|
+
|
91
|
+
# Check that we got it, returning `nil` if we don't
|
92
|
+
return nil if values[:gemspec_path].nil?
|
93
|
+
|
94
|
+
# Load up the gemspec ad version
|
95
|
+
values[:spec] = ::Gem::Specification::load values[:gemspec_path].to_s
|
96
|
+
|
97
|
+
# Get the name from the spec
|
98
|
+
values[:name] = values[:spec].name
|
99
|
+
|
100
|
+
# Get the version from the spec
|
101
|
+
values[:version] = QB::Package::Version.from_gem_version \
|
102
|
+
values[:spec].version
|
103
|
+
|
104
|
+
# Construct the resource instance and return it.
|
105
|
+
new **values
|
106
|
+
end # #from_root_path
|
107
|
+
|
108
|
+
|
109
|
+
# Like {.from_root_path} but raises an error if the path is not a gem
|
110
|
+
# root directory.
|
111
|
+
#
|
112
|
+
# @param path see .from_root_path
|
113
|
+
#
|
114
|
+
# @return [QB::Package::Gem]
|
115
|
+
#
|
116
|
+
# @raise [QB::FSStateError]
|
117
|
+
# - If `path` is not a directory.
|
118
|
+
#
|
119
|
+
# - If `path` is not a Gem directory.
|
120
|
+
#
|
121
|
+
def from_root_path! path
|
122
|
+
from_root_path( path ).tap { |gem|
|
123
|
+
if gem.nil?
|
124
|
+
raise QB::FSStateError.squished <<-END
|
125
|
+
Path #{ path.inspect } does not appear to be the root directory
|
126
|
+
of a Ruby gem.
|
127
|
+
END
|
128
|
+
end
|
129
|
+
}
|
130
|
+
end # #from_root_path!
|
131
|
+
|
132
|
+
|
133
|
+
|
134
|
+
end # class << self (Eigenclass)
|
135
|
+
|
136
|
+
|
137
|
+
# Properties
|
138
|
+
# =====================================================================
|
139
|
+
|
140
|
+
# Principle Properties
|
141
|
+
# ---------------------------------------------------------------------
|
142
|
+
|
143
|
+
prop :gemspec_path,
|
144
|
+
type: t.file_path
|
145
|
+
|
146
|
+
prop :spec,
|
147
|
+
type: ::Gem::Specification
|
148
|
+
|
149
|
+
|
150
|
+
# Instance Methods
|
151
|
+
# ======================================================================
|
152
|
+
|
153
|
+
end # class QB::Package::Gem
|
data/lib/qb/package/version.rb
CHANGED
@@ -1,434 +1,457 @@
|
|
1
|
+
# Requirements
|
2
|
+
# =======================================================================
|
3
|
+
|
4
|
+
# Stdlib
|
5
|
+
# -----------------------------------------------------------------------
|
1
6
|
require 'time'
|
2
7
|
|
3
|
-
|
8
|
+
# Deps
|
9
|
+
# -----------------------------------------------------------------------
|
10
|
+
|
11
|
+
# Project / Package
|
12
|
+
# -----------------------------------------------------------------------
|
13
|
+
require 'qb/util/docker_mixin'
|
14
|
+
require 'qb/util/resource'
|
15
|
+
|
16
|
+
|
17
|
+
# Refinements
|
18
|
+
# =======================================================================
|
4
19
|
|
20
|
+
require 'nrser/refinements/types'
|
5
21
|
using NRSER::Types
|
6
22
|
|
7
|
-
require 'qb/util/docker_mixin'
|
8
23
|
|
9
|
-
|
10
|
-
|
11
|
-
# An attempt to unify NPM and Gem version schemes to a reasonable extend,
|
12
|
-
# and hopefully cover whatever else the cat may drag in.
|
13
|
-
#
|
14
|
-
# Intended to be immutable for practical purposes.
|
15
|
-
#
|
16
|
-
class Version < NRSER::Meta::Props::Base
|
17
|
-
|
18
|
-
# Mixins
|
19
|
-
# =====================================================================
|
20
|
-
|
21
|
-
include QB::Util::DockerMixin
|
22
|
-
|
23
|
-
|
24
|
-
# Constants
|
25
|
-
# =====================================================================
|
26
|
-
|
27
|
-
NUMBER_SEGMENT = t.non_neg_int
|
28
|
-
NAME_SEGMENT = t.str
|
29
|
-
MIXED_SEGMENT = t.union NUMBER_SEGMENT, NAME_SEGMENT
|
30
|
-
|
31
|
-
|
32
|
-
# Props
|
33
|
-
# =====================================================================
|
24
|
+
# Declarations
|
25
|
+
# =======================================================================
|
34
26
|
|
35
|
-
|
36
|
-
|
37
|
-
prop :minor, type: NUMBER_SEGMENT, default: 0
|
38
|
-
prop :patch, type: NUMBER_SEGMENT, default: 0
|
39
|
-
prop :prerelease, type: t.array(MIXED_SEGMENT), default: []
|
40
|
-
prop :build, type: t.array(MIXED_SEGMENT), default: []
|
27
|
+
module QB; end
|
28
|
+
class QB::Package < QB::Util::Resource; end
|
41
29
|
|
42
|
-
prop :release, type: t.str, source: :@release
|
43
|
-
prop :level, type: t.str, source: :@level
|
44
|
-
prop :is_release, type: t.bool, source: :release?
|
45
|
-
prop :is_prerelease, type: t.bool, source: :prerelease?
|
46
|
-
prop :is_build, type: t.bool, source: :build?
|
47
|
-
prop :is_dev, type: t.bool, source: :dev?
|
48
|
-
prop :is_rc, type: t.bool, source: :rc?
|
49
|
-
prop :has_level, type: t.bool, source: :level?
|
50
|
-
prop :semver, type: t.str, source: :semver
|
51
|
-
prop :docker_tag, type: t.str, source: :docker_tag
|
52
|
-
prop :build_commit, type: t.maybe(t.str), source: :build_commit
|
53
|
-
prop :is_build_dirty, type: t.maybe(t.bool), source: :build_dirty?
|
54
30
|
|
31
|
+
# Definitions
|
32
|
+
# =======================================================================
|
55
33
|
|
56
|
-
|
57
|
-
|
34
|
+
# An attempt to unify NPM and Gem version schemes to a reasonable extend,
|
35
|
+
# and hopefully cover whatever else the cat may drag in.
|
36
|
+
#
|
37
|
+
# Intended to be immutable for practical purposes.
|
38
|
+
#
|
39
|
+
class QB::Package::Version < QB::Util::Resource
|
40
|
+
|
41
|
+
# Mixins
|
42
|
+
# =====================================================================
|
43
|
+
|
44
|
+
include QB::Util::DockerMixin
|
45
|
+
|
46
|
+
|
47
|
+
# Constants
|
48
|
+
# =====================================================================
|
49
|
+
|
50
|
+
NUMBER_SEGMENT = t.non_neg_int
|
51
|
+
NAME_SEGMENT = t.str
|
52
|
+
MIXED_SEGMENT = t.union NUMBER_SEGMENT, NAME_SEGMENT
|
53
|
+
|
54
|
+
|
55
|
+
# Props
|
56
|
+
# =====================================================================
|
58
57
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
# empty.
|
205
|
-
#
|
206
|
-
def prerelease?
|
207
|
-
!prerelease.empty?
|
208
|
-
end
|
209
|
-
|
210
|
-
|
211
|
-
# @return [Boolean]
|
212
|
-
# True if any build segments are present (stuff after '+' character
|
213
|
-
# in SemVer / "NPM" format). Tests if {@build} is empty.
|
214
|
-
#
|
215
|
-
# As of writing, we don't have a way to convey build segments in
|
216
|
-
# "Gem" version format, so this will always be false when loading a
|
217
|
-
# Gem version.
|
218
|
-
#
|
219
|
-
def build?
|
220
|
-
!build.empty?
|
221
|
-
end
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
# @todo Document build_dirty? method.
|
226
|
-
#
|
227
|
-
# @param [type] arg_name
|
228
|
-
# @todo Add name param description.
|
229
|
-
#
|
230
|
-
# @return [return_type]
|
231
|
-
# @todo Document return value.
|
232
|
-
#
|
233
|
-
def build_dirty?
|
234
|
-
if build?
|
235
|
-
build.include? 'dirty'
|
236
|
-
end
|
237
|
-
end # #build_dirty?
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
# @return [Boolean]
|
242
|
-
# True if self is a prerelease version that starts with a string that
|
243
|
-
# we consider the 'level'.
|
244
|
-
#
|
245
|
-
def level?
|
246
|
-
!level.nil?
|
247
|
-
end
|
248
|
-
|
249
|
-
|
250
|
-
# @return [Boolean]
|
251
|
-
# True if this version is a dev prerelease (first prerelease element
|
252
|
-
# is 'dev').
|
253
|
-
#
|
254
|
-
def dev?
|
255
|
-
level == 'dev'
|
256
|
-
end
|
257
|
-
|
258
|
-
|
259
|
-
# @return [Boolean]
|
260
|
-
# True if this version is a release candidate (first prerelease element
|
261
|
-
# is 'rc').
|
262
|
-
#
|
263
|
-
def rc?
|
264
|
-
level == 'rc'
|
265
|
-
end
|
266
|
-
|
267
|
-
|
268
|
-
# Derived Properties
|
269
|
-
# ---------------------------------------------------------------------
|
270
|
-
|
271
|
-
# @return [String]
|
272
|
-
# The Semver version string
|
273
|
-
# (`Major.minor.patch-prerelease+build` format).
|
274
|
-
#
|
275
|
-
def semver
|
276
|
-
result = release
|
277
|
-
|
278
|
-
unless prerelease.empty?
|
279
|
-
result += "-#{ prerelease.join '.' }"
|
280
|
-
end
|
281
|
-
|
282
|
-
unless build.empty?
|
283
|
-
result += "+#{ build.join '.' }"
|
284
|
-
end
|
285
|
-
|
286
|
-
result
|
287
|
-
end # #semver
|
288
|
-
|
289
|
-
alias_method :normalized, :semver
|
290
|
-
|
291
|
-
|
292
|
-
# @todo Document commit method.
|
293
|
-
#
|
294
|
-
# @param [type] arg_name
|
295
|
-
# @todo Add name param description.
|
296
|
-
#
|
297
|
-
# @return [return_type]
|
298
|
-
# @todo Document return value.
|
299
|
-
#
|
300
|
-
def build_commit
|
301
|
-
if build?
|
302
|
-
build.find { |seg| seg =~ /[0-9a-f]{7}/ }
|
303
|
-
end
|
304
|
-
end # #commit
|
305
|
-
|
306
|
-
|
307
|
-
# Docker image tag for the version.
|
308
|
-
#
|
309
|
-
# See {QB::Util::DockerMixin::ClassMethods#to_docker_tag}.
|
310
|
-
#
|
311
|
-
# @return [String]
|
312
|
-
#
|
313
|
-
def docker_tag
|
314
|
-
self.class.to_docker_tag semver
|
315
|
-
end # #docker_tag
|
316
|
-
|
317
|
-
|
318
|
-
# Related Versions
|
319
|
-
# ---------------------------------------------------------------------
|
320
|
-
#
|
321
|
-
# Functions that construct new version instances based on the current
|
322
|
-
# one as well as additional information provided.
|
323
|
-
#
|
324
|
-
|
325
|
-
# @return [QB::Package::Version]
|
326
|
-
# A new {QB::Package::Version} created from {#release}. Even if `self`
|
327
|
-
# *is* a release version already, still returns a new instance.
|
328
|
-
#
|
329
|
-
def release_version
|
330
|
-
self.class.from_string release
|
331
|
-
end # #release_version
|
332
|
-
|
333
|
-
|
334
|
-
# Return a new {QB::Package::Version} with build information added.
|
335
|
-
#
|
336
|
-
# @return [QB::Package::Version]
|
337
|
-
#
|
338
|
-
def build_version branch: nil, ref: nil, time: nil, dirty: nil
|
339
|
-
time = self.class.to_time_segment(time) unless time.nil?
|
340
|
-
|
341
|
-
segments = [
|
342
|
-
branch,
|
343
|
-
ref,
|
344
|
-
('dirty' if dirty),
|
345
|
-
time,
|
346
|
-
].reject &:nil?
|
347
|
-
|
348
|
-
if segments.empty?
|
349
|
-
raise ArgumentError,
|
350
|
-
"Need to provide at least one of branch, ref, time."
|
58
|
+
prop :raw, type: t.maybe(t.str), default: nil
|
59
|
+
prop :major, type: NUMBER_SEGMENT
|
60
|
+
prop :minor, type: NUMBER_SEGMENT, default: 0
|
61
|
+
prop :patch, type: NUMBER_SEGMENT, default: 0
|
62
|
+
prop :prerelease, type: t.array(MIXED_SEGMENT), default: []
|
63
|
+
prop :build, type: t.array(MIXED_SEGMENT), default: []
|
64
|
+
|
65
|
+
prop :release, type: t.str, source: :@release
|
66
|
+
prop :level, type: t.str, source: :@level
|
67
|
+
prop :is_release, type: t.bool, source: :release?
|
68
|
+
prop :is_prerelease, type: t.bool, source: :prerelease?
|
69
|
+
prop :is_build, type: t.bool, source: :build?
|
70
|
+
prop :is_dev, type: t.bool, source: :dev?
|
71
|
+
prop :is_rc, type: t.bool, source: :rc?
|
72
|
+
prop :has_level, type: t.bool, source: :level?
|
73
|
+
prop :semver, type: t.str, source: :semver
|
74
|
+
prop :docker_tag, type: t.str, source: :docker_tag
|
75
|
+
prop :build_commit, type: t.maybe(t.str), source: :build_commit
|
76
|
+
prop :is_build_dirty, type: t.maybe(t.bool), source: :build_dirty?
|
77
|
+
|
78
|
+
|
79
|
+
# Attributes
|
80
|
+
# =====================================================================
|
81
|
+
|
82
|
+
attr_reader :release,
|
83
|
+
:level
|
84
|
+
|
85
|
+
|
86
|
+
# Class Methods
|
87
|
+
# =====================================================================
|
88
|
+
|
89
|
+
# Utilities
|
90
|
+
# ---------------------------------------------------------------------
|
91
|
+
|
92
|
+
# @return [String]
|
93
|
+
# Time formatted to be stuck in a version segment per Semver spec.
|
94
|
+
# We also strip out '-' to avoid possible parsing weirdness.
|
95
|
+
def self.to_time_segment time
|
96
|
+
time.utc.iso8601.gsub /[^0-9A-Za-z]/, ''
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
# Instance Builders
|
101
|
+
# ---------------------------------------------------------------------
|
102
|
+
|
103
|
+
# Create a Version instance from a {Gem::Version}.
|
104
|
+
#
|
105
|
+
# @param [Gem::Version] version
|
106
|
+
#
|
107
|
+
# @return [QB::Package::Version]
|
108
|
+
#
|
109
|
+
def self.from_gem_version version
|
110
|
+
# release segments are everything before a string
|
111
|
+
release_segments = version.segments.take_while { |seg|
|
112
|
+
!seg.is_a?(String)
|
113
|
+
}
|
114
|
+
|
115
|
+
# We don't support > 3 release segments to make life somewhat
|
116
|
+
# reasonable. Yeah, I think I've seen projects do it. We'll cross that
|
117
|
+
# bridge if and when we get to it.
|
118
|
+
if release_segments.length > 3
|
119
|
+
raise ArgumentError,
|
120
|
+
"We don't handle releases with more than 3 segments " +
|
121
|
+
"(found #{ release_segments.inspect } in #{ version })"
|
122
|
+
end
|
123
|
+
|
124
|
+
prerelease_segments = version.segments[release_segments.length..-1]
|
125
|
+
|
126
|
+
new raw: version.to_s,
|
127
|
+
major: release_segments[0] || 0,
|
128
|
+
minor: release_segments[1] || 0,
|
129
|
+
patch: release_segments[2] || 0,
|
130
|
+
prerelease: prerelease_segments,
|
131
|
+
build: []
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.from_npm_version version
|
135
|
+
stmt = NRSER.squish <<-END
|
136
|
+
var Semver = require('semver');
|
137
|
+
|
138
|
+
console.log(
|
139
|
+
JSON.stringify(
|
140
|
+
Semver(#{ JSON.dump version })
|
141
|
+
)
|
142
|
+
);
|
143
|
+
END
|
144
|
+
|
145
|
+
parse = JSON.load Cmds.new(
|
146
|
+
"node --eval %s", args: [stmt], chdir: QB::ROOT
|
147
|
+
).out!
|
148
|
+
|
149
|
+
new raw: version,
|
150
|
+
major: parse['major'],
|
151
|
+
minor: parse['minor'],
|
152
|
+
patch: parse['patch'],
|
153
|
+
prerelease: parse['prerelease'],
|
154
|
+
build: parse['build']
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
# Parse Docker image tag version into a string. Reverse of
|
159
|
+
# {QB::Package::Version#docker_tag}.
|
160
|
+
#
|
161
|
+
# @param [String] version
|
162
|
+
# String version to parse.
|
163
|
+
#
|
164
|
+
# @return [QB::Package::Version]
|
165
|
+
#
|
166
|
+
def self.from_docker_tag version
|
167
|
+
from_string(version.gsub('_', '+')).merge raw: version
|
168
|
+
end # .from_docker_tag
|
169
|
+
|
170
|
+
|
171
|
+
# Parse string version into an instance. Accept Semver, Ruby Gem and
|
172
|
+
# Docker image tag formats.
|
173
|
+
#
|
174
|
+
# @param [String]
|
175
|
+
# String version to parse.
|
176
|
+
#
|
177
|
+
# @return [QB::Package::Version]
|
178
|
+
#
|
179
|
+
def self.from_string string
|
180
|
+
if string.include? '_'
|
181
|
+
self.from_docker_tag string
|
182
|
+
elsif string.include?( '-' ) || string.include?( '+' )
|
183
|
+
self.from_npm_version string
|
184
|
+
else
|
185
|
+
self.from_gem_version Gem::Version.new(string)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
# Constructor
|
191
|
+
# =====================================================================
|
192
|
+
|
193
|
+
# Construct a new Version
|
194
|
+
def initialize **values
|
195
|
+
super **values
|
196
|
+
|
197
|
+
@release = [major, minor, patch].join '.'
|
198
|
+
|
199
|
+
@level = t.match prerelease[0], {
|
200
|
+
t.is(nil) => ->(_) {
|
201
|
+
if build.empty?
|
202
|
+
'release'
|
351
203
|
end
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
204
|
+
},
|
205
|
+
|
206
|
+
NAME_SEGMENT => ->(str) { str },
|
207
|
+
|
208
|
+
NUMBER_SEGMENT => ->(int) { nil },
|
209
|
+
}
|
210
|
+
end
|
211
|
+
|
212
|
+
|
213
|
+
# Instance Methods
|
214
|
+
# =====================================================================
|
215
|
+
|
216
|
+
# Tests
|
217
|
+
# ---------------------------------------------------------------------
|
218
|
+
|
219
|
+
# @return [Boolean]
|
220
|
+
# True if this version is a release (no prerelease or build values).
|
221
|
+
#
|
222
|
+
def release?
|
223
|
+
prerelease.empty? && build.empty?
|
224
|
+
end
|
225
|
+
|
226
|
+
|
227
|
+
# @return [Boolean]
|
228
|
+
# True if any prerelease segments are present (stuff after '-' in
|
229
|
+
# SemVer / "NPM" format, or the first string segment and anything
|
230
|
+
# following it in "Gem" format). Tests if {@prerelease} is not
|
231
|
+
# empty.
|
232
|
+
#
|
233
|
+
def prerelease?
|
234
|
+
!prerelease.empty?
|
235
|
+
end
|
236
|
+
|
237
|
+
|
238
|
+
# @return [Boolean]
|
239
|
+
# True if any build segments are present (stuff after '+' character
|
240
|
+
# in SemVer / "NPM" format). Tests if {@build} is empty.
|
241
|
+
#
|
242
|
+
# As of writing, we don't have a way to convey build segments in
|
243
|
+
# "Gem" version format, so this will always be false when loading a
|
244
|
+
# Gem version.
|
245
|
+
#
|
246
|
+
def build?
|
247
|
+
!build.empty?
|
248
|
+
end
|
249
|
+
|
250
|
+
|
251
|
+
# @todo Document build_dirty? method.
|
252
|
+
#
|
253
|
+
# @param [type] arg_name
|
254
|
+
# @todo Add name param description.
|
255
|
+
#
|
256
|
+
# @return [return_type]
|
257
|
+
# @todo Document return value.
|
258
|
+
#
|
259
|
+
def build_dirty?
|
260
|
+
if build?
|
261
|
+
build.include? 'dirty'
|
262
|
+
end
|
263
|
+
end # #build_dirty?
|
264
|
+
|
265
|
+
|
266
|
+
# @return [Boolean]
|
267
|
+
# True if self is a prerelease version that starts with a string that
|
268
|
+
# we consider the 'level'.
|
269
|
+
#
|
270
|
+
def level?
|
271
|
+
!level.nil?
|
272
|
+
end
|
273
|
+
|
274
|
+
|
275
|
+
# @return [Boolean]
|
276
|
+
# True if this version is a dev prerelease (first prerelease element
|
277
|
+
# is 'dev').
|
278
|
+
#
|
279
|
+
def dev?
|
280
|
+
level == 'dev'
|
281
|
+
end
|
282
|
+
|
283
|
+
|
284
|
+
# @return [Boolean]
|
285
|
+
# True if this version is a release candidate (first prerelease element
|
286
|
+
# is 'rc').
|
287
|
+
#
|
288
|
+
def rc?
|
289
|
+
level == 'rc'
|
290
|
+
end
|
291
|
+
|
292
|
+
|
293
|
+
# Derived Properties
|
294
|
+
# ---------------------------------------------------------------------
|
295
|
+
|
296
|
+
# @return [String]
|
297
|
+
# The Semver version string
|
298
|
+
# (`Major.minor.patch-prerelease+build` format).
|
299
|
+
#
|
300
|
+
def semver
|
301
|
+
result = release
|
302
|
+
|
303
|
+
unless prerelease.empty?
|
304
|
+
result += "-#{ prerelease.join '.' }"
|
305
|
+
end
|
306
|
+
|
307
|
+
unless build.empty?
|
308
|
+
result += "+#{ build.join '.' }"
|
309
|
+
end
|
310
|
+
|
311
|
+
result
|
312
|
+
end # #semver
|
313
|
+
|
314
|
+
alias_method :normalized, :semver
|
315
|
+
|
316
|
+
|
317
|
+
# @todo Document commit method.
|
318
|
+
#
|
319
|
+
# @param [type] arg_name
|
320
|
+
# @todo Add name param description.
|
321
|
+
#
|
322
|
+
# @return [return_type]
|
323
|
+
# @todo Document return value.
|
324
|
+
#
|
325
|
+
def build_commit
|
326
|
+
if build?
|
327
|
+
build.find { |seg| seg =~ /[0-9a-f]{7}/ }
|
328
|
+
end
|
329
|
+
end # #commit
|
330
|
+
|
331
|
+
|
332
|
+
# Docker image tag for the version.
|
333
|
+
#
|
334
|
+
# See {QB::Util::DockerMixin::ClassMethods#to_docker_tag}.
|
335
|
+
#
|
336
|
+
# @return [String]
|
337
|
+
#
|
338
|
+
def docker_tag
|
339
|
+
self.class.to_docker_tag semver
|
340
|
+
end # #docker_tag
|
341
|
+
|
342
|
+
|
343
|
+
# Related Versions
|
344
|
+
# ---------------------------------------------------------------------
|
345
|
+
#
|
346
|
+
# Functions that construct new version instances based on the current
|
347
|
+
# one as well as additional information provided.
|
348
|
+
#
|
349
|
+
|
350
|
+
# @return [QB::Package::Version]
|
351
|
+
# A new {QB::Package::Version} created from {#release}. Even if `self`
|
352
|
+
# *is* a release version already, still returns a new instance.
|
353
|
+
#
|
354
|
+
def release_version
|
355
|
+
self.class.from_string release
|
356
|
+
end # #release_version
|
357
|
+
|
358
|
+
|
359
|
+
# Return a new {QB::Package::Version} with build information added.
|
360
|
+
#
|
361
|
+
# @return [QB::Package::Version]
|
362
|
+
#
|
363
|
+
def build_version branch: nil, ref: nil, time: nil, dirty: nil
|
364
|
+
time = self.class.to_time_segment(time) unless time.nil?
|
365
|
+
|
366
|
+
segments = [
|
367
|
+
branch,
|
368
|
+
ref,
|
369
|
+
('dirty' if dirty),
|
370
|
+
time,
|
371
|
+
].reject &:nil?
|
372
|
+
|
373
|
+
if segments.empty?
|
374
|
+
raise ArgumentError,
|
375
|
+
"Need to provide at least one of branch, ref, time."
|
376
|
+
end
|
377
|
+
|
378
|
+
merge raw: nil, build: segments
|
379
|
+
end
|
380
|
+
|
381
|
+
|
382
|
+
# @return [QB::Package::Version]
|
383
|
+
# A new {QB::Package::Version} created from {#release} and
|
384
|
+
# {#prerelease} data, but without any build information.
|
385
|
+
#
|
386
|
+
def prerelease_version
|
387
|
+
merge raw: nil, build: []
|
388
|
+
end # #prerelease_version
|
389
|
+
|
390
|
+
|
391
|
+
|
392
|
+
# Language Interface
|
393
|
+
# =====================================================================
|
394
|
+
|
395
|
+
# Test for equality.
|
396
|
+
#
|
397
|
+
# Compares classes then {QB::Package::Version#to_a} results.
|
398
|
+
#
|
399
|
+
# @param [Object] other
|
400
|
+
# Object to compare to self.
|
401
|
+
#
|
402
|
+
# @return [Boolean]
|
403
|
+
# True if self and other are considered equal.
|
404
|
+
#
|
405
|
+
def == other
|
406
|
+
other.class == self.class &&
|
407
|
+
other.to_a == self.to_a
|
408
|
+
end # #==
|
409
|
+
|
410
|
+
|
411
|
+
# Return array of the version elements in order from greatest to least
|
412
|
+
# precedence.
|
413
|
+
#
|
414
|
+
# This is considered the representative structure for the object's data,
|
415
|
+
# from which all other values are dependently derived, and is used in
|
416
|
+
# {#==}, {#hash} and {#eql?}.
|
417
|
+
#
|
418
|
+
# @example
|
419
|
+
#
|
420
|
+
# version = QB::Package::Version.from_string(
|
421
|
+
# "0.1.2-rc.10+master.0ab1c3d"
|
422
|
+
# )
|
423
|
+
#
|
424
|
+
# version.to_a
|
425
|
+
# # => [0, 1, 2, ['rc', 10], ['master', '0ab1c3d']]
|
426
|
+
#
|
427
|
+
# QB::Package::Version.from_string('1').to_a
|
428
|
+
# # => [1, nil, nil, [], []]
|
429
|
+
#
|
430
|
+
# @return [Array]
|
431
|
+
#
|
432
|
+
def to_a
|
433
|
+
[
|
434
|
+
major,
|
435
|
+
minor,
|
436
|
+
patch,
|
437
|
+
prerelease,
|
438
|
+
build,
|
439
|
+
]
|
440
|
+
end # #to_a
|
441
|
+
|
442
|
+
|
443
|
+
def hash
|
444
|
+
to_a.hash
|
445
|
+
end
|
446
|
+
|
447
|
+
|
448
|
+
def eql? other
|
449
|
+
self == other && self.hash == other.hash
|
450
|
+
end
|
451
|
+
|
452
|
+
|
453
|
+
def to_s
|
454
|
+
"#<QB::Package::Version #{ @raw }>"
|
455
|
+
end
|
456
|
+
|
457
|
+
end # class QB::Package::Version
|