qb 0.3.25 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|