qb 0.3.25 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/ansible.cfg +10 -1
- data/exe/.qb_interop_receive +3 -10
- data/exe/qb +8 -2
- data/lib/python/qb/__init__.py +6 -0
- data/{roles/qb/ruby/rspec/setup/tasks/persistence.yml → lib/python/qb/ansible/__init__.py} +0 -0
- data/lib/python/qb/ansible/modules/__init__.py +0 -0
- data/lib/python/qb/ansible/modules/docker/__init__.py +0 -0
- data/lib/python/qb/ansible/modules/docker/client.py +177 -0
- data/lib/python/qb/ansible/modules/docker/image_manager.py +754 -0
- data/lib/python/qb/ipc/__init__.py +0 -0
- data/lib/python/qb/ipc/stdio/__init__.py +99 -0
- data/lib/python/qb/ipc/stdio/logging.py +151 -0
- data/lib/qb.rb +3 -3
- data/lib/qb/ansible/cmds/playbook.rb +5 -14
- data/lib/qb/ansible/env.rb +36 -6
- data/lib/qb/ansible/module.rb +396 -152
- data/lib/qb/ansible/module/response.rb +195 -0
- data/lib/qb/ansible/modules.rb +42 -0
- data/lib/qb/ansible/modules/docker/image.rb +273 -0
- data/lib/qb/cli.rb +5 -18
- data/lib/qb/cli/run.rb +2 -2
- data/lib/qb/data.rb +22 -0
- data/lib/qb/data/immutable.rb +39 -0
- data/lib/qb/docker.rb +2 -0
- data/lib/qb/docker/cli.rb +430 -0
- data/lib/qb/docker/image.rb +207 -0
- data/lib/qb/docker/image/name.rb +309 -0
- data/lib/qb/docker/image/tag.rb +113 -0
- data/lib/qb/docker/repo.rb +0 -0
- data/lib/qb/errors.rb +17 -3
- data/lib/qb/execution.rb +83 -0
- data/lib/qb/ipc.rb +48 -0
- data/lib/qb/ipc/stdio.rb +32 -0
- data/lib/qb/ipc/stdio/client.rb +267 -0
- data/lib/qb/ipc/stdio/server.rb +229 -0
- data/lib/qb/ipc/stdio/server/in_service.rb +18 -0
- data/lib/qb/ipc/stdio/server/log_service.rb +168 -0
- data/lib/qb/ipc/stdio/server/out_service.rb +20 -0
- data/lib/qb/ipc/stdio/server/service.rb +229 -0
- data/lib/qb/options.rb +360 -502
- data/lib/qb/options/option.rb +293 -115
- data/lib/qb/options/option/option_parser_concern.rb +228 -0
- data/lib/qb/options/types.rb +73 -0
- data/lib/qb/package.rb +0 -1
- data/lib/qb/package/version.rb +179 -58
- data/lib/qb/package/version/from.rb +192 -51
- data/lib/qb/package/version/leveled.rb +1 -1
- data/lib/qb/path.rb +3 -2
- data/lib/qb/repo/git.rb +9 -85
- data/lib/qb/role/default_dir.rb +2 -2
- data/lib/qb/role/errors.rb +2 -8
- data/lib/qb/util.rb +1 -2
- data/lib/qb/util/bundler.rb +73 -43
- data/lib/qb/util/decorators.rb +99 -0
- data/lib/qb/util/interop.rb +7 -8
- data/lib/qb/util/resource.rb +12 -13
- data/lib/qb/version.rb +10 -0
- data/library/path_facts +5 -10
- data/library/qb.module.rb +105 -0
- data/library/stream +6 -26
- data/load/ansible/module/autorun.rb +25 -0
- data/load/ansible/module/script.rb +123 -0
- data/load/rebundle.rb +39 -0
- data/plugins/filter/dict_filters.py +56 -0
- data/plugins/{filter_plugins/path_plugins.py → filter/path_filters.py} +0 -0
- data/plugins/{filter_plugins/ruby_interop_plugins.py → filter/ruby_interop_filters.py} +1 -17
- data/plugins/{filter_plugins/string_plugins.py → filter/string_filters.py} +1 -20
- data/plugins/{filter_plugins/version_plugins.py → filter/version_filters.py} +3 -18
- data/plugins/{lookup_plugins/every.py → lookup/every_lookups.py} +0 -0
- data/plugins/{lookup_plugins/resolve.py → lookup/resolve_lookups.py} +0 -0
- data/plugins/{lookup_plugins/version.py → lookup/version_lookups.py} +0 -16
- data/plugins/test/dict_tests.py +36 -0
- data/plugins/test/string_tests.py +36 -0
- data/qb.gemspec +7 -3
- data/roles/nrser.rb/library/set_fact_with_ruby.rb +3 -9
- data/roles/nrser.state_mate/library/state +3 -17
- data/roles/qb/call/meta/qb.yml +1 -1
- data/roles/qb/dev/ref/repo/git/meta/qb.yml +1 -1
- data/roles/qb/{ruby/rspec/setup → docker/mac/kubernetes}/defaults/main.yml +1 -1
- data/roles/qb/{ruby/rspec/setup → docker/mac/kubernetes}/meta/main.yml +3 -2
- data/roles/qb/{ruby/rspec/setup → docker/mac/kubernetes}/meta/qb.yml +12 -7
- data/roles/qb/docker/mac/kubernetes/tasks/main.yml +45 -0
- data/roles/qb/git/check/clean/meta/qb.yml +1 -1
- data/roles/qb/git/ignore/meta/qb +10 -3
- data/roles/qb/git/submodule/update/library/git_submodule_update +17 -27
- data/roles/qb/github/pages/setup/meta/qb.yml +1 -1
- data/roles/qb/labs/atom/apm/meta/qb.yml +1 -1
- data/roles/qb/osx/git/change_case/meta/qb.yml +1 -1
- data/roles/qb/osx/notif/meta/qb.yml +1 -1
- data/roles/qb/pkg/bump/library/bump +4 -16
- data/roles/qb/role/qb/defaults/main.yml +2 -0
- data/roles/qb/role/qb/meta/qb.yml +10 -5
- data/roles/qb/role/qb/templates/qb.yml.j2 +7 -2
- data/roles/qb/role/templates/library/module.rb.j2 +12 -23
- data/roles/qb/role/templates/meta/main.yml.j2 +14 -1
- data/roles/qb/ruby/bundler/meta/qb.yml +1 -1
- data/roles/qb/ruby/dependency/meta/qb.yml +1 -1
- data/roles/qb/ruby/gem/bin_stubs/meta/qb.yml +1 -1
- data/roles/qb/ruby/gem/bin_stubs/templates/console +8 -2
- data/roles/qb/ruby/gem/build/meta/qb.yml +1 -1
- data/roles/qb/ruby/gem/new/meta/qb.yml +1 -1
- data/roles/qb/ruby/nrser/rspex/generate/meta/qb.yml +5 -5
- data/roles/qb/ruby/nrser/rspex/issue/meta/qb.yml +1 -1
- data/roles/qb/ruby/yard/clean/meta/qb.yml +1 -1
- data/roles/qb/ruby/yard/config/library/yard.get_output_dir +5 -15
- data/roles/qb/ruby/yard/config/meta/qb.yml +1 -1
- data/roles/qb/ruby/yard/setup/meta/qb.yml +1 -1
- metadata +71 -22
- data/lib/qb/ansible_module.rb +0 -5
- data/lib/qb/util/stdio.rb +0 -187
- data/roles/qb/ruby/rspec/setup/tasks/main.yml +0 -4
@@ -0,0 +1,73 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Requirements
|
5
|
+
# =======================================================================
|
6
|
+
|
7
|
+
# Stdlib
|
8
|
+
# -----------------------------------------------------------------------
|
9
|
+
|
10
|
+
# Deps
|
11
|
+
# -----------------------------------------------------------------------
|
12
|
+
|
13
|
+
require 'nrser/refinements/types'
|
14
|
+
|
15
|
+
# Project / Package
|
16
|
+
# -----------------------------------------------------------------------
|
17
|
+
|
18
|
+
|
19
|
+
# Refinements
|
20
|
+
# =======================================================================
|
21
|
+
|
22
|
+
using NRSER::Types
|
23
|
+
|
24
|
+
|
25
|
+
# Definitions
|
26
|
+
# =======================================================================
|
27
|
+
|
28
|
+
# Custom types, available by factory name in QB metadata. Neat huh?!
|
29
|
+
#
|
30
|
+
module QB
|
31
|
+
class Options
|
32
|
+
module Types
|
33
|
+
extend t::Factory
|
34
|
+
|
35
|
+
def_factory :glob do
|
36
|
+
t.array t.path, name: 'FileGlob', from_s: ->( glob ) {
|
37
|
+
if glob.start_with? '//'
|
38
|
+
glob = NRSER.git_root( Dir.getwd ).join( glob[2..-1] ).to_s
|
39
|
+
end
|
40
|
+
|
41
|
+
Dir[glob]
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def_factory(
|
47
|
+
:qb_default_dir_strategy,
|
48
|
+
aliases: [ :default_dir_strategy ],
|
49
|
+
) do |name: 'QBDefaultDirStrategy', **options|
|
50
|
+
t.one_of \
|
51
|
+
t.nil,
|
52
|
+
t.false,
|
53
|
+
'cwd',
|
54
|
+
'git_root',
|
55
|
+
t.shape( 'exe' => t.path ),
|
56
|
+
t.shape( 'find_up' => t.rel_path ),
|
57
|
+
t.shape( 'from_role' => t.non_empty_str ),
|
58
|
+
name: name,
|
59
|
+
**options
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
def_factory(
|
64
|
+
:qb_default_dir,
|
65
|
+
aliases: [ :default_dir ],
|
66
|
+
) do |name: 'QBDefaultDir', **options|
|
67
|
+
t.one_of \
|
68
|
+
qb_default_dir_strategy,
|
69
|
+
t.array( qb_default_dir_strategy ),
|
70
|
+
**options
|
71
|
+
end
|
72
|
+
|
73
|
+
end; end; end # module QB::Options::Types
|
data/lib/qb/package.rb
CHANGED
data/lib/qb/package/version.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
1
4
|
# Requirements
|
2
5
|
# =======================================================================
|
3
6
|
|
@@ -17,20 +20,10 @@ require 'qb/util/resource'
|
|
17
20
|
# Refinements
|
18
21
|
# =======================================================================
|
19
22
|
|
20
|
-
require 'nrser/refinements'
|
21
|
-
using NRSER
|
22
|
-
|
23
23
|
require 'nrser/refinements/types'
|
24
24
|
using NRSER::Types
|
25
25
|
|
26
26
|
|
27
|
-
# Declarations
|
28
|
-
# =======================================================================
|
29
|
-
|
30
|
-
module QB; end
|
31
|
-
class QB::Package < QB::Util::Resource; end
|
32
|
-
|
33
|
-
|
34
27
|
# Definitions
|
35
28
|
# =======================================================================
|
36
29
|
|
@@ -39,7 +32,104 @@ class QB::Package < QB::Util::Resource; end
|
|
39
32
|
#
|
40
33
|
# Intended to be immutable for practical purposes.
|
41
34
|
#
|
42
|
-
|
35
|
+
# Based off [SemVer 2][] and - in particular - the [Node semver package][]
|
36
|
+
# interpretation / implementation, though we don't use that package at all
|
37
|
+
# at this point (sub-shelling out became too expensive, and explorations into
|
38
|
+
# Ruby Racer, etc. didn't pan out (don't remember exactly why)).
|
39
|
+
#
|
40
|
+
# [SemVer 2]: https://semver.org/spec/v2.0.0.html
|
41
|
+
# [Node semver package]: https://www.npmjs.com/package/semver
|
42
|
+
#
|
43
|
+
# Let's start the show with some fun...
|
44
|
+
#
|
45
|
+
# Terminology
|
46
|
+
# ----------------------------------------------------------------------------
|
47
|
+
#
|
48
|
+
# Working off what's in the [SemVer 2][] spec as much as possible.
|
49
|
+
#
|
50
|
+
# We're going to start from the bottom and build up...
|
51
|
+
#
|
52
|
+
#
|
53
|
+
# ### Identifiers
|
54
|
+
#
|
55
|
+
# *Identifiers* ([SemVer 2][] spec term) are the atoms of the version:
|
56
|
+
# the values that will not be further divided.
|
57
|
+
#
|
58
|
+
# They come in two types (my terms):
|
59
|
+
#
|
60
|
+
# 1. *Number Identifiers*
|
61
|
+
#
|
62
|
+
# Non-negative integers. Their string representations may not include
|
63
|
+
# leading zeros.
|
64
|
+
#
|
65
|
+
# 2. *Name Identifiers*
|
66
|
+
#
|
67
|
+
# Non-empty strings that contain only `a-z`, `A-Z` and `-` and are
|
68
|
+
# **not** number identifiers.
|
69
|
+
#
|
70
|
+
# All identifiers must be exclusively one type or the other.
|
71
|
+
#
|
72
|
+
# Parse and validate identifiers with
|
73
|
+
# {QB::Package::Version::From.identifier_for}.
|
74
|
+
#
|
75
|
+
#
|
76
|
+
# ### Segments
|
77
|
+
#
|
78
|
+
# *Segments* (my term) are sequences of zero or more *identifiers*.
|
79
|
+
#
|
80
|
+
# In string representation, the *identifiers* in a *segment* are separated
|
81
|
+
# by the dot (`.`) character.
|
82
|
+
#
|
83
|
+
# > **NOTE**
|
84
|
+
# >
|
85
|
+
# > As identifiers can not be empty, a segment's string representation may not
|
86
|
+
# > start or end with `.`, and may not contain consecutive `.`.
|
87
|
+
#
|
88
|
+
# There are three types of segments:
|
89
|
+
#
|
90
|
+
# 1. *Release Segment*
|
91
|
+
#
|
92
|
+
# Composed of exactly three *number identifiers*:
|
93
|
+
#
|
94
|
+
# 1. *Major*
|
95
|
+
# 2. *Minor*
|
96
|
+
# 3. *Patch*
|
97
|
+
#
|
98
|
+
# > **NOTE**
|
99
|
+
# >
|
100
|
+
# > Ruby's Gem version format doesn't require anything but the *major*
|
101
|
+
# > identifier, in which case we default missing ones to `0`.
|
102
|
+
#
|
103
|
+
# 2. *Prerelease Segment*
|
104
|
+
#
|
105
|
+
# Composed of zero or more *identifiers* - number or name, in any order.
|
106
|
+
#
|
107
|
+
# 3. *Build Segment*
|
108
|
+
#
|
109
|
+
# Composed of zero or more *identifiers* - number or name, in any order.
|
110
|
+
#
|
111
|
+
#
|
112
|
+
# ### Versions
|
113
|
+
#
|
114
|
+
# A *version* is exactly one release segment, prerelease segment and build
|
115
|
+
# segment, in which the prerelease and build segments may be empty as noted
|
116
|
+
# above.
|
117
|
+
#
|
118
|
+
# In SemVer string representation, the release segment is always present, and
|
119
|
+
# a non-empty prerelease segment may follow it, separated by a `-` character.
|
120
|
+
#
|
121
|
+
# A non-empty build segment may follow those, separated by a `+` character.
|
122
|
+
#
|
123
|
+
module QB
|
124
|
+
class Package < QB::Util::Resource
|
125
|
+
class Version < QB::Util::Resource
|
126
|
+
|
127
|
+
# Sub-Tree Requirements
|
128
|
+
# ========================================================================
|
129
|
+
|
130
|
+
require_relative './version/leveled'
|
131
|
+
require_relative './version/from'
|
132
|
+
|
43
133
|
|
44
134
|
# Mixins
|
45
135
|
# =====================================================================
|
@@ -51,9 +141,23 @@ class QB::Package::Version < QB::Util::Resource
|
|
51
141
|
# Constants
|
52
142
|
# =====================================================================
|
53
143
|
|
144
|
+
# Pattern to match string *identifiers* that are version "numlets" (the
|
145
|
+
# non-negative integer number part of version "numbers").
|
146
|
+
#
|
147
|
+
# @return [Regexp]
|
148
|
+
#
|
149
|
+
NUMBER_IDENTIFIER_RE = /\A(?:0|(?:[1-9]\d*))\z/
|
150
|
+
|
151
|
+
|
152
|
+
# What separates *identifiers* (the base undivided values).
|
153
|
+
#
|
154
|
+
# @return [String]
|
155
|
+
#
|
156
|
+
IDENTIFIER_SEPARATOR = '.'
|
157
|
+
|
54
158
|
NUMBER_SEGMENT = t.non_neg_int
|
55
|
-
NAME_SEGMENT = t.str
|
56
|
-
MIXED_SEGMENT = t.
|
159
|
+
NAME_SEGMENT = t.str & /\A[0-9A-Za-z\-]+\z/
|
160
|
+
MIXED_SEGMENT = t.xor NUMBER_SEGMENT, NAME_SEGMENT
|
57
161
|
|
58
162
|
|
59
163
|
# Reasonably simple regular expression to extract things that might be
|
@@ -89,27 +193,53 @@ class QB::Package::Version < QB::Util::Resource
|
|
89
193
|
# Props
|
90
194
|
# =====================================================================
|
91
195
|
|
92
|
-
prop :raw, type:
|
93
|
-
|
94
|
-
|
95
|
-
prop :
|
96
|
-
|
97
|
-
prop :
|
98
|
-
|
99
|
-
|
100
|
-
prop :
|
101
|
-
|
102
|
-
|
103
|
-
prop :
|
104
|
-
|
105
|
-
|
106
|
-
prop :
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
196
|
+
prop :raw, type: t.maybe(t.str),
|
197
|
+
default: nil
|
198
|
+
|
199
|
+
prop :major, type: NUMBER_SEGMENT
|
200
|
+
|
201
|
+
prop :minor, type: NUMBER_SEGMENT,
|
202
|
+
default: 0
|
203
|
+
|
204
|
+
prop :patch, type: NUMBER_SEGMENT,
|
205
|
+
default: 0
|
206
|
+
|
207
|
+
prop :revision, type: t.array( NUMBER_SEGMENT ),
|
208
|
+
default: ->{ [] }
|
209
|
+
|
210
|
+
prop :prerelease, type: t.array( MIXED_SEGMENT ),
|
211
|
+
default: ->{ [] }
|
212
|
+
|
213
|
+
prop :build, type: t.array( MIXED_SEGMENT ),
|
214
|
+
default: ->{ [] }
|
215
|
+
|
216
|
+
|
217
|
+
# Derived Props
|
218
|
+
# --------------------------------------------------------------------------
|
219
|
+
|
220
|
+
prop :release, type: t.str,
|
221
|
+
source: :@release
|
222
|
+
|
223
|
+
prop :is_release, type: t.bool,
|
224
|
+
source: :release?
|
225
|
+
|
226
|
+
prop :is_prerelease, type: t.bool,
|
227
|
+
source: :prerelease?
|
228
|
+
|
229
|
+
prop :is_build, type: t.bool,
|
230
|
+
source: :build?
|
231
|
+
|
232
|
+
prop :semver, type: t.str,
|
233
|
+
source: :semver
|
234
|
+
|
235
|
+
prop :docker_tag, type: t.str,
|
236
|
+
source: :docker_tag
|
237
|
+
|
238
|
+
prop :build_commit, type: t.maybe(t.str),
|
239
|
+
source: :build_commit
|
240
|
+
|
241
|
+
prop :is_build_dirty, type: t.maybe(t.bool),
|
242
|
+
source: :build_dirty?
|
113
243
|
|
114
244
|
|
115
245
|
# Class Methods
|
@@ -201,26 +331,24 @@ class QB::Package::Version < QB::Util::Resource
|
|
201
331
|
end
|
202
332
|
|
203
333
|
|
204
|
-
#
|
334
|
+
# Is the build "dirty"?
|
205
335
|
#
|
206
|
-
# @
|
207
|
-
# @todo Add name param description.
|
208
|
-
#
|
209
|
-
# @return [return_type]
|
210
|
-
# @todo Document return value.
|
336
|
+
# @return [Boolean]
|
211
337
|
#
|
212
338
|
def build_dirty?
|
213
339
|
if build?
|
214
|
-
build.include? 'dirty'
|
340
|
+
build.any? { |seg| seg.is_a?( String ) && seg.include?( 'dirty' ) }
|
215
341
|
end
|
216
342
|
end # #build_dirty?
|
217
343
|
|
344
|
+
alias_method :dirty?, :build_dirty?
|
345
|
+
|
218
346
|
|
219
347
|
# Derived Properties
|
220
348
|
# ---------------------------------------------------------------------
|
221
349
|
|
222
350
|
def release
|
223
|
-
[major, minor, patch].join '.'
|
351
|
+
[major, minor, patch, *revision].join '.'
|
224
352
|
end
|
225
353
|
|
226
354
|
|
@@ -291,22 +419,21 @@ class QB::Package::Version < QB::Util::Resource
|
|
291
419
|
#
|
292
420
|
# @return [QB::Package::Version]
|
293
421
|
#
|
294
|
-
def build_version branch: nil, ref: nil, time: nil, dirty: nil
|
295
|
-
time = self.class.to_time_segment(time) unless time.nil?
|
422
|
+
def build_version *build, branch: nil, ref: nil, time: nil, dirty: nil
|
296
423
|
|
297
|
-
|
424
|
+
repo_segments = [
|
298
425
|
branch,
|
299
426
|
ref,
|
300
427
|
('dirty' if dirty),
|
301
|
-
time,
|
302
|
-
].
|
428
|
+
(self.class.to_time_segment(time) if dirty && time),
|
429
|
+
].compact
|
303
430
|
|
304
|
-
if
|
431
|
+
if repo_segments.empty?
|
305
432
|
raise ArgumentError,
|
306
|
-
"Need to provide at least one of branch, ref,
|
433
|
+
"Need to provide at least one of branch, ref, dirty."
|
307
434
|
end
|
308
435
|
|
309
|
-
merge raw: nil, build:
|
436
|
+
merge raw: nil, build: [*build, repo_segments.join( '-' )]
|
310
437
|
end
|
311
438
|
|
312
439
|
|
@@ -367,6 +494,7 @@ class QB::Package::Version < QB::Util::Resource
|
|
367
494
|
major,
|
368
495
|
minor,
|
369
496
|
patch,
|
497
|
+
revision,
|
370
498
|
prerelease,
|
371
499
|
build,
|
372
500
|
]
|
@@ -387,11 +515,4 @@ class QB::Package::Version < QB::Util::Resource
|
|
387
515
|
"#<QB::Package::Version #{ @raw }>"
|
388
516
|
end
|
389
517
|
|
390
|
-
end # class QB::Package::Version
|
391
|
-
|
392
|
-
|
393
|
-
# Post-Processing
|
394
|
-
# =======================================================================
|
395
|
-
|
396
|
-
require 'qb/package/version/leveled'
|
397
|
-
require 'qb/package/version/from'
|
518
|
+
end; end; end # class QB::Package::Version
|
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
|
3
4
|
# Refinements
|
4
5
|
# =======================================================================
|
5
6
|
|
6
|
-
using NRSER
|
7
7
|
using NRSER::Types
|
8
8
|
|
9
9
|
|
@@ -51,91 +51,232 @@ module QB::Package::Version::From
|
|
51
51
|
#
|
52
52
|
# @return [QB::Package::Version]
|
53
53
|
#
|
54
|
-
def self.gemver
|
55
|
-
|
54
|
+
def self.gemver source
|
55
|
+
gem_version = case source
|
56
|
+
when ::Gem::Version
|
57
|
+
source
|
58
|
+
else
|
59
|
+
::Gem::Version.new source.to_s
|
60
|
+
end
|
56
61
|
|
57
62
|
# release segments are everything before a string
|
58
|
-
release_segments =
|
63
|
+
release_segments = gem_version.segments.take_while { |seg|
|
59
64
|
!seg.is_a?(String)
|
60
65
|
}
|
61
66
|
|
62
|
-
|
63
|
-
# reasonable. Yeah, I think I've seen projects do it. We'll cross that
|
64
|
-
# bridge if and when we get to it.
|
65
|
-
if release_segments.length > 3
|
66
|
-
raise ArgumentError,
|
67
|
-
"We don't handle releases with more than 3 segments " +
|
68
|
-
"(found #{ release_segments.inspect } in #{ version })"
|
69
|
-
end
|
70
|
-
|
71
|
-
prerelease_segments = version.segments[release_segments.length..-1]
|
67
|
+
prerelease_segments = gem_version.segments[release_segments.length..-1]
|
72
68
|
|
73
69
|
prop_values \
|
74
|
-
raw:
|
75
|
-
major: release_segments[0]
|
70
|
+
raw: source.to_s,
|
71
|
+
major: release_segments[0],
|
76
72
|
minor: release_segments[1] || 0,
|
77
73
|
patch: release_segments[2] || 0,
|
74
|
+
revision: release_segments[3..-1] || [],
|
78
75
|
prerelease: prerelease_segments,
|
79
76
|
build: []
|
80
77
|
end
|
81
78
|
|
82
79
|
singleton_class.send :alias_method, :gem_version, :gemver
|
83
|
-
|
84
|
-
|
85
|
-
def self.
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
80
|
+
|
81
|
+
|
82
|
+
def self.split_identifiers string
|
83
|
+
string.split QB::Package::Version::IDENTIFIER_SEPARATOR
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
# Parse and/or validate version *identifiers*.
|
88
|
+
#
|
89
|
+
# See {QB::Package::Version} for details on *identifiers*.
|
90
|
+
#
|
91
|
+
# @param [String | Integer] value
|
92
|
+
# A value that is either already an *identifier* or a string that can
|
93
|
+
# be parsed into one.
|
94
|
+
#
|
95
|
+
# @return [String | Integer]
|
96
|
+
# A valid *identifier*.
|
97
|
+
#
|
98
|
+
def self.identifier_for value
|
99
|
+
case value
|
100
|
+
when QB::Package::Version::NUMBER_IDENTIFIER_RE
|
101
|
+
value.to_i
|
102
|
+
when QB::Package::Version::MIXED_SEGMENT
|
103
|
+
value
|
104
|
+
else
|
105
|
+
raise ArgumentError.new binding.erb <<~END
|
106
|
+
Can't parse identifier <%= value.inspect %>
|
107
|
+
|
108
|
+
Expected one of:
|
109
|
+
|
110
|
+
1. <%= QB::Package::Version::NUMBER_IDENTIFIER_RE %>
|
111
|
+
2. <%= QB::Package::Version::MIXED_SEGMENT %>
|
112
|
+
|
113
|
+
END
|
114
|
+
end
|
115
|
+
end # .identifier_for
|
116
|
+
|
117
|
+
|
118
|
+
def self.segment_for string
|
119
|
+
split_identifiers( string ).map { |s| identifier_for s }
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
# Load a SemVer¹ string into a {QB::Package::Version}.
|
124
|
+
#
|
125
|
+
# @see https://semver.org
|
126
|
+
#
|
127
|
+
# > **¹**
|
128
|
+
# > Through a combination of need, failure and frustration we are a
|
129
|
+
# > *wee bit* looser than the SemVer spec. This really comes from needing
|
130
|
+
# > to be able to handle more than three *release segments* because:
|
131
|
+
# >
|
132
|
+
# > 1. Well, some projects use more than three and we can't change that.
|
133
|
+
# > 2. It seemed over-complicated to add another "almost-semver" parsing
|
134
|
+
# > option.
|
135
|
+
# >
|
136
|
+
# > Details below. You can enforce our best attempt at pure SemVer with
|
137
|
+
# > the `strict:` keyword option.
|
138
|
+
#
|
139
|
+
# ##### Gory Details #####
|
140
|
+
#
|
141
|
+
# Oh, semver... what a pain you're been.
|
142
|
+
#
|
143
|
+
# Right now, I just finished writing our own parser. It probably has a lot
|
144
|
+
# of problems and I can't imagine it conforms to the spec even where it's
|
145
|
+
# meant to. I didn't want to go this road, it was out of desperation.
|
146
|
+
#
|
147
|
+
# First, QB was shelling-out to Node and using it's [semver][Node semver]
|
148
|
+
# package, since that seems to kind of be the de-facto reference
|
149
|
+
# implementation of the spec.
|
150
|
+
#
|
151
|
+
# That was far too slow to process large lists of version like you might
|
152
|
+
# get from `git tag`, and it means we depended on Node and had to bundle
|
153
|
+
# the `semver` package in or install it otherwise, which was a pain for
|
154
|
+
# a single function call.
|
155
|
+
#
|
156
|
+
# Yeah, there are other ways to go about it, but they all suck too.
|
157
|
+
#
|
158
|
+
# Next I tried the [semver2][Ruby semver2] Ruby gem. It never struck me as
|
159
|
+
# super solid, and when faced with `1.2.3.4-pre`-style versions it just
|
160
|
+
# tossed everything after the `3` and didn't mention it, which led me to
|
161
|
+
# toss it too.
|
162
|
+
#
|
163
|
+
# This happen when I realized we couldn't side-step "fourth release segment"
|
164
|
+
# because I needed the system to handle OpenResty's Docker image versions,
|
165
|
+
# which are `M.m.p.r` format, leading to add the
|
166
|
+
# {QB::Package::Version#revision} property and write my own parsing logic
|
167
|
+
# here.
|
168
|
+
#
|
169
|
+
# [Node semver]: https://www.npmjs.com/package/semver
|
170
|
+
#
|
171
|
+
# @param [#to_s] source
|
172
|
+
# Where to get the source string.
|
173
|
+
#
|
174
|
+
# @param [Boolean] strict:
|
175
|
+
# When `true`, we attempt to adhere strictly to the SemVer spec, raising
|
176
|
+
# if we find any departures.
|
177
|
+
#
|
178
|
+
# @raise [ArgumentError]
|
179
|
+
# 1. If there are **less than** `3` *release segments*.
|
180
|
+
#
|
181
|
+
# This helps us not loading things like `2018-new-stuff` into a
|
182
|
+
# version, as might be found in a Git tag.
|
183
|
+
#
|
184
|
+
# It does not let us avoid loading `2018.10.11-new-stuff` info a
|
185
|
+
# version, so fair warning.
|
186
|
+
#
|
187
|
+
# 2. If `strict: true` and there are not **exactly** `3`
|
188
|
+
# *release segments*.
|
189
|
+
#
|
190
|
+
def self.semver source, strict: false
|
191
|
+
source = source.to_s unless source.is_a?( String )
|
95
192
|
|
96
|
-
|
97
|
-
"node --eval %s", args: [stmt], chdir: QB::ROOT
|
98
|
-
).out!
|
193
|
+
identifier_for_ref = method :identifier_for
|
99
194
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
195
|
+
if source.include?( '-' ) && source.include?( '+' )
|
196
|
+
release_str, _, rest = source.partition '-'
|
197
|
+
pre_str, _, build_str = rest.partition '+'
|
198
|
+
elsif source.include?( '-' )
|
199
|
+
release_str, _, pre_str = source.partition '-'
|
200
|
+
build_str = ''
|
201
|
+
elsif source.include?( '+' )
|
202
|
+
release_str, _, build_str = source.partition '+'
|
203
|
+
pre_str = ''
|
204
|
+
else
|
205
|
+
release_str = source
|
206
|
+
pre_str = build_str = ''
|
207
|
+
end
|
208
|
+
|
209
|
+
release_segs, pre_segs, build_segs = \
|
210
|
+
[release_str, pre_str, build_str].map { |str|
|
211
|
+
split_identifiers( str ).map &identifier_for_ref
|
212
|
+
}
|
213
|
+
|
214
|
+
# Check release segments length
|
215
|
+
if strict && release_segs.length != 3
|
216
|
+
raise NRSER::ArgumentError.new \
|
217
|
+
"Strict SemVer versions *MUST* have at exactly 3 release segments",
|
218
|
+
source: source,
|
219
|
+
release_segments: release_segs
|
220
|
+
|
221
|
+
elsif release_segs.length < 3
|
222
|
+
raise NRSER::ArgumentError.new \
|
223
|
+
"SemVer versions *MUST* have at lease 3 release segments",
|
224
|
+
source: source,
|
225
|
+
release_segments: release_segs
|
226
|
+
|
227
|
+
end
|
228
|
+
|
229
|
+
prop_values **{
|
230
|
+
raw: source,
|
231
|
+
major: release_segs[0],
|
232
|
+
minor: release_segs[1],
|
233
|
+
patch: release_segs[2],
|
234
|
+
revision: release_segs[3..-1] || [],
|
235
|
+
prerelease: pre_segs,
|
236
|
+
build: build_segs,
|
237
|
+
}.compact
|
107
238
|
end
|
108
239
|
|
109
|
-
|
240
|
+
|
241
|
+
def npm_version source
|
242
|
+
semver source, strict: true
|
243
|
+
end
|
110
244
|
|
111
245
|
|
112
246
|
# Parse Docker image tag version and create an instance.
|
113
247
|
#
|
114
|
-
# @param [
|
248
|
+
# @param [#to_s] source
|
115
249
|
# String version to parse.
|
116
250
|
#
|
117
251
|
# @return [QB::Package::Version]
|
118
252
|
#
|
119
|
-
def self.docker_tag
|
120
|
-
|
253
|
+
def self.docker_tag source
|
254
|
+
source = source.to_s unless source.is_a?( String )
|
255
|
+
self.string( source.gsub( '_', '+' ) ).merge raw: source
|
121
256
|
end # .docker_tag
|
122
257
|
|
123
258
|
|
124
259
|
# Parse string version into an instance. Accept Semver, Ruby Gem and
|
125
260
|
# Docker image tag formats.
|
126
261
|
#
|
127
|
-
# @param [
|
262
|
+
# @param [#to_s] source
|
128
263
|
# String version to parse.
|
129
264
|
#
|
130
265
|
# @return [QB::Package::Version]
|
131
266
|
#
|
132
|
-
def self.string
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
267
|
+
def self.string source
|
268
|
+
source = source.to_s unless source.is_a?( String )
|
269
|
+
|
270
|
+
t.non_empty_str.check source
|
271
|
+
|
272
|
+
if source.include? '_'
|
273
|
+
docker_tag source
|
274
|
+
elsif ( source.include?( '-' ) ||
|
275
|
+
source.include?( '+' ) ) &&
|
276
|
+
source =~ /\A\d+\.\d+\.\d+/
|
277
|
+
semver source
|
137
278
|
else
|
138
|
-
|
279
|
+
gemver source
|
139
280
|
end
|
140
281
|
end
|
141
282
|
|
@@ -148,8 +289,8 @@ module QB::Package::Version::From
|
|
148
289
|
string object
|
149
290
|
when Hash
|
150
291
|
prop_values **object
|
151
|
-
when Gem::Version
|
152
|
-
|
292
|
+
when ::Gem::Version
|
293
|
+
gemver object
|
153
294
|
else
|
154
295
|
raise TypeError.new binding.erb <<-END
|
155
296
|
`object` must be String, Hash or Gem::Version
|