qb 0.3.7 → 0.3.8
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 +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
|