versionian 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/README.adoc +874 -0
- data/Rakefile +12 -0
- data/docs/Gemfile +8 -0
- data/docs/_guides/custom-schemes.adoc +110 -0
- data/docs/_guides/index.adoc +12 -0
- data/docs/_pages/component-types.adoc +151 -0
- data/docs/_pages/declarative-schemes.adoc +260 -0
- data/docs/_pages/index.adoc +15 -0
- data/docs/_pages/range-matching.adoc +102 -0
- data/docs/_pages/schemes.adoc +68 -0
- data/docs/_references/api.adoc +251 -0
- data/docs/_references/index.adoc +13 -0
- data/docs/_references/schemes.adoc +410 -0
- data/docs/_tutorials/getting-started.adoc +119 -0
- data/docs/_tutorials/index.adoc +11 -0
- data/docs/index.adoc +287 -0
- data/lib/versionian/component_definition.rb +71 -0
- data/lib/versionian/component_types/base.rb +19 -0
- data/lib/versionian/component_types/date_part.rb +41 -0
- data/lib/versionian/component_types/enum.rb +32 -0
- data/lib/versionian/component_types/float.rb +23 -0
- data/lib/versionian/component_types/hash.rb +21 -0
- data/lib/versionian/component_types/integer.rb +23 -0
- data/lib/versionian/component_types/postfix.rb +51 -0
- data/lib/versionian/component_types/prerelease.rb +70 -0
- data/lib/versionian/component_types/registry.rb +35 -0
- data/lib/versionian/component_types/string.rb +21 -0
- data/lib/versionian/errors/invalid_scheme_error.rb +7 -0
- data/lib/versionian/errors/invalid_version_error.rb +7 -0
- data/lib/versionian/errors/parse_error.rb +7 -0
- data/lib/versionian/parsers/declarative.rb +214 -0
- data/lib/versionian/scheme_loader.rb +102 -0
- data/lib/versionian/scheme_registry.rb +34 -0
- data/lib/versionian/schemes/calver.rb +94 -0
- data/lib/versionian/schemes/composite.rb +82 -0
- data/lib/versionian/schemes/declarative.rb +138 -0
- data/lib/versionian/schemes/pattern.rb +236 -0
- data/lib/versionian/schemes/semantic.rb +136 -0
- data/lib/versionian/schemes/solover.rb +121 -0
- data/lib/versionian/schemes/wendtver.rb +141 -0
- data/lib/versionian/version.rb +6 -0
- data/lib/versionian/version_component.rb +28 -0
- data/lib/versionian/version_identifier.rb +121 -0
- data/lib/versionian/version_range.rb +61 -0
- data/lib/versionian/version_scheme.rb +68 -0
- data/lib/versionian.rb +64 -0
- data/lib/versius.rb +5 -0
- data/sig/versius.rbs +4 -0
- metadata +157 -0
data/README.adoc
ADDED
|
@@ -0,0 +1,874 @@
|
|
|
1
|
+
= Versionian: Declarative versioning schemes
|
|
2
|
+
:toclevels:3
|
|
3
|
+
:toc:
|
|
4
|
+
|
|
5
|
+
image:https://img.shields.io/gem/v/versionian.svg[RubyGems Version]
|
|
6
|
+
image:https://img.shields.io/github/license/lutaml/versionian.svg[License]
|
|
7
|
+
image:https://github.com/lutaml/versionian/actions/workflows/rake.yml/badge.svg["Build", link="https://github.com/lutaml/versionian/actions/workflows/rake.yml"]
|
|
8
|
+
|
|
9
|
+
== Purpose
|
|
10
|
+
|
|
11
|
+
Versionian is a Ruby library for declaring, parsing, comparing, and rendering
|
|
12
|
+
version schemes. It provides model-driven primitives for defining how versions
|
|
13
|
+
work, supporting semantic versioning, calendar versioning, and unlimited custom
|
|
14
|
+
schemes through declarative YAML configuration.
|
|
15
|
+
|
|
16
|
+
=== Why Versionian?
|
|
17
|
+
|
|
18
|
+
Ruby's built-in `Gem::Version` handles semantic versioning well, but real-world
|
|
19
|
+
projects use diverse versioning schemes that `Gem::Version` cannot parse or
|
|
20
|
+
compare correctly:
|
|
21
|
+
|
|
22
|
+
* **Calendar versioning** (`2024.01.17`, `2024.03`) - `Gem::Version` parses these but cannot enforce date validation
|
|
23
|
+
* **Single-number versioning** (`5`, `5+hotfix`, `5-beta`) - `Gem::Version` treats postfixes incorrectly
|
|
24
|
+
* **Human-readable versions** (`Alpha.1.5`, `Beta.2.0`) - `Gem::Version` cannot compare these
|
|
25
|
+
* **Hash-based versions** (`2024.01.abc123`) - `Gem::Version` has no concept of commit hashes
|
|
26
|
+
* **Custom schemes** - Every project has unique versioning needs
|
|
27
|
+
|
|
28
|
+
=== How Versionian is different
|
|
29
|
+
|
|
30
|
+
[cols="h,1,1",options="header"]
|
|
31
|
+
|===
|
|
32
|
+
|Feature |Gem::Version |Versionian
|
|
33
|
+
|
|
34
|
+
|Supported schemes
|
|
35
|
+
|Semantic versioning only
|
|
36
|
+
|4 built-in schemes, unlimited custom
|
|
37
|
+
|
|
38
|
+
|Extensibility
|
|
39
|
+
|Not extensible
|
|
40
|
+
|Define custom schemes via declarative YAML or Ruby
|
|
41
|
+
|
|
42
|
+
|Component types
|
|
43
|
+
|Integers and dot-separated strings
|
|
44
|
+
|Integer, Float, Enum, DatePart, Hash, Postfix, Prerelease, String, Wildcard
|
|
45
|
+
|
|
46
|
+
|Comparison strategy
|
|
47
|
+
|Fixed SemVer rules
|
|
48
|
+
|Per-scheme comparison (lexicographic arrays)
|
|
49
|
+
|
|
50
|
+
|Validation
|
|
51
|
+
|Basic format validation
|
|
52
|
+
|Type-aware validation (date ranges, enum values, hash formats)
|
|
53
|
+
|
|
54
|
+
|Programmatic building
|
|
55
|
+
|Not supported
|
|
56
|
+
|Build versions from component values
|
|
57
|
+
|===
|
|
58
|
+
|
|
59
|
+
Versionian fills the gap between `Gem::Version`'s semantic versioning focus and the diverse versioning needs of real-world projects.
|
|
60
|
+
|
|
61
|
+
== Features
|
|
62
|
+
|
|
63
|
+
* <<built-in-schemes,Built-in version schemes>>
|
|
64
|
+
* <<declarative-schemes,Declarative custom schemes>>
|
|
65
|
+
* <<discovering-schemes,Discovering available schemes>>
|
|
66
|
+
* <<component-types,Extensible component type system>>
|
|
67
|
+
* <<range-matching,Version range matching>>
|
|
68
|
+
* <<yaml-config,YAML configuration with safe loading>>
|
|
69
|
+
* <<custom-schemes,Custom Ruby schemes>>
|
|
70
|
+
|
|
71
|
+
== Quick start
|
|
72
|
+
|
|
73
|
+
Get started with Versionian in three steps:
|
|
74
|
+
|
|
75
|
+
.Require the library
|
|
76
|
+
[source,ruby]
|
|
77
|
+
----
|
|
78
|
+
require 'versionian'
|
|
79
|
+
----
|
|
80
|
+
|
|
81
|
+
.Parse and compare versions
|
|
82
|
+
[source,ruby]
|
|
83
|
+
----
|
|
84
|
+
# Use built-in semantic versioning
|
|
85
|
+
scheme = Versionian.get_scheme(:semantic)
|
|
86
|
+
|
|
87
|
+
version = scheme.parse("1.12.0")
|
|
88
|
+
puts version.to_s # => "1.12.0"
|
|
89
|
+
|
|
90
|
+
# Compare versions
|
|
91
|
+
result = scheme.compare("1.12.0", "2.0.0")
|
|
92
|
+
puts result # => -1 (less than)
|
|
93
|
+
----
|
|
94
|
+
|
|
95
|
+
.Declare a custom scheme via YAML
|
|
96
|
+
[source,yaml]
|
|
97
|
+
----
|
|
98
|
+
# schemes/my_scheme.yaml
|
|
99
|
+
name: my_scheme
|
|
100
|
+
type: declarative
|
|
101
|
+
description: Custom version scheme
|
|
102
|
+
components:
|
|
103
|
+
- name: major
|
|
104
|
+
type: integer
|
|
105
|
+
separator: "."
|
|
106
|
+
- name: minor
|
|
107
|
+
type: integer
|
|
108
|
+
separator: "."
|
|
109
|
+
- name: patch
|
|
110
|
+
type: integer
|
|
111
|
+
optional: true
|
|
112
|
+
----
|
|
113
|
+
|
|
114
|
+
[source,ruby]
|
|
115
|
+
----
|
|
116
|
+
# Load and use custom scheme
|
|
117
|
+
scheme = Versionian::SchemeLoader.from_yaml_file('schemes/my_scheme.yaml')
|
|
118
|
+
Versionian.register_scheme(:my_scheme, scheme)
|
|
119
|
+
|
|
120
|
+
version = scheme.parse("1.2.3")
|
|
121
|
+
puts version.major # => 1
|
|
122
|
+
----
|
|
123
|
+
|
|
124
|
+
== Architecture
|
|
125
|
+
|
|
126
|
+
Versionian follows a model-driven architecture where version schemes define
|
|
127
|
+
their own parsing, comparison, and rendering behavior.
|
|
128
|
+
|
|
129
|
+
=== Lexicographic array comparison
|
|
130
|
+
|
|
131
|
+
Versionian uses lexicographic array comparison instead of weighted integer sums.
|
|
132
|
+
Each version stores a `comparable_array` where each element represents a
|
|
133
|
+
component in comparison order:
|
|
134
|
+
|
|
135
|
+
[source,ruby]
|
|
136
|
+
----
|
|
137
|
+
# Version stores array for comparison
|
|
138
|
+
version.comparable_array # => [2024, 1, 17, :rc, 1]
|
|
139
|
+
|
|
140
|
+
# Comparison is element-by-element
|
|
141
|
+
[2024, 1, 17] <=> [2024, 1, 18] # => -1
|
|
142
|
+
[2024, 1, 17] <=> [2024, 2, 1] # => -1
|
|
143
|
+
[2024, 1, 17] <=> [2024, 1, 17] # => 0
|
|
144
|
+
----
|
|
145
|
+
|
|
146
|
+
This approach avoids integer overflow, provides better performance, and allows
|
|
147
|
+
each component type to define its own comparison semantics.
|
|
148
|
+
|
|
149
|
+
=== Parse versus build
|
|
150
|
+
|
|
151
|
+
Versionian provides two ways to create version identifiers:
|
|
152
|
+
|
|
153
|
+
* **Parse**: Extract components from a version string
|
|
154
|
+
* **Build**: Create a version from component values
|
|
155
|
+
|
|
156
|
+
[source,ruby]
|
|
157
|
+
----
|
|
158
|
+
# Parse: from string
|
|
159
|
+
version = scheme.parse("1.12.0")
|
|
160
|
+
|
|
161
|
+
# Build: from component values
|
|
162
|
+
version = scheme.build(major: 1, minor: 12, patch: 0)
|
|
163
|
+
----
|
|
164
|
+
|
|
165
|
+
Both methods return the same `VersionIdentifier` object with a
|
|
166
|
+
`comparable_array` for comparison.
|
|
167
|
+
|
|
168
|
+
=== Object model
|
|
169
|
+
|
|
170
|
+
.Versionian object model
|
|
171
|
+
[source]
|
|
172
|
+
----
|
|
173
|
+
Versionian::VersionScheme (abstract)
|
|
174
|
+
├── parse(version_string) -> VersionIdentifier
|
|
175
|
+
├── build(component_values) -> VersionIdentifier
|
|
176
|
+
├── compare_arrays(a, b) -> Integer
|
|
177
|
+
├── render(version) -> String
|
|
178
|
+
└── matches_range?(version_string, range) -> Boolean
|
|
179
|
+
|
|
180
|
+
Implemented Schemes:
|
|
181
|
+
├── Semantic (SemVer)
|
|
182
|
+
├── CalVer (Calendar versioning)
|
|
183
|
+
├── SoloVer (Single number with postfix)
|
|
184
|
+
├── WendtVer (Auto-incrementing)
|
|
185
|
+
└── Declarative (Custom segment-based)
|
|
186
|
+
|
|
187
|
+
VersionIdentifier objects:
|
|
188
|
+
├── raw_string (original input)
|
|
189
|
+
├── scheme (reference to scheme)
|
|
190
|
+
├── components (array of VersionComponent)
|
|
191
|
+
└── comparable_array (for lexicographic comparison)
|
|
192
|
+
|
|
193
|
+
Component type registry:
|
|
194
|
+
├── Integer (numeric sequences)
|
|
195
|
+
├── Float (IEEE754 floats)
|
|
196
|
+
├── String (arbitrary text)
|
|
197
|
+
├── Enum (ordered stages)
|
|
198
|
+
├── DatePart (calendar components)
|
|
199
|
+
├── Prerelease (SemVer prereleases)
|
|
200
|
+
├── Postfix (SoloVer suffixes)
|
|
201
|
+
├── Hash (git commit hashes)
|
|
202
|
+
└── Custom types (user-defined)
|
|
203
|
+
----
|
|
204
|
+
|
|
205
|
+
== Installation
|
|
206
|
+
|
|
207
|
+
Add this line to your application's Gemfile:
|
|
208
|
+
|
|
209
|
+
[source,ruby]
|
|
210
|
+
----
|
|
211
|
+
gem 'versionian'
|
|
212
|
+
----
|
|
213
|
+
|
|
214
|
+
And then execute:
|
|
215
|
+
|
|
216
|
+
[source,shell]
|
|
217
|
+
----
|
|
218
|
+
bundle install
|
|
219
|
+
----
|
|
220
|
+
|
|
221
|
+
Or install it yourself as:
|
|
222
|
+
|
|
223
|
+
[source,shell]
|
|
224
|
+
----
|
|
225
|
+
gem install versionian
|
|
226
|
+
----
|
|
227
|
+
|
|
228
|
+
== Features
|
|
229
|
+
|
|
230
|
+
[[built-in-schemes]]
|
|
231
|
+
=== Built-in schemes
|
|
232
|
+
|
|
233
|
+
[[built-in-schemes-general]]
|
|
234
|
+
==== General
|
|
235
|
+
|
|
236
|
+
Versionian provides built-in support for common versioning schemes.
|
|
237
|
+
Each scheme is pre-registered and ready to use.
|
|
238
|
+
|
|
239
|
+
[[built-in-schemes-usage]]
|
|
240
|
+
==== Usage
|
|
241
|
+
|
|
242
|
+
[source,ruby]
|
|
243
|
+
----
|
|
244
|
+
scheme = Versionian.get_scheme(:semantic) <1>
|
|
245
|
+
version = scheme.parse("1.12.0") <2>
|
|
246
|
+
result = scheme.compare("1.12.0", "2.0.0") <3>
|
|
247
|
+
----
|
|
248
|
+
<1> Get a built-in scheme by name.
|
|
249
|
+
<2> Parse a version string.
|
|
250
|
+
<3> Compare two versions (returns -1, 0, or 1).
|
|
251
|
+
|
|
252
|
+
Where,
|
|
253
|
+
|
|
254
|
+
`scheme`:: A VersionScheme instance.
|
|
255
|
+
`version`:: A VersionIdentifier object.
|
|
256
|
+
`result`:: Integer: -1 (less than), 0 (equal), 1 (greater than).
|
|
257
|
+
|
|
258
|
+
[[built-in-schemes-list]]
|
|
259
|
+
==== Supported schemes
|
|
260
|
+
|
|
261
|
+
[cols="1,1,1",options="header"]
|
|
262
|
+
|===
|
|
263
|
+
|Scheme |Format |Description
|
|
264
|
+
|
|
265
|
+
|SemVer
|
|
266
|
+
|`MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]`
|
|
267
|
+
|Semantic versioning per semver.org
|
|
268
|
+
|
|
269
|
+
|CalVer
|
|
270
|
+
|`YYYY.MM.DD`, `YYYY.MM`, `YY.MM.DD`
|
|
271
|
+
|Calendar versioning with date validation
|
|
272
|
+
|
|
273
|
+
|SoloVer
|
|
274
|
+
|`N[+|-]postfix`
|
|
275
|
+
|Single number with optional postfix
|
|
276
|
+
|
|
277
|
+
|WendtVer
|
|
278
|
+
|`MAJOR.MINOR.PATCH.BUILD`
|
|
279
|
+
|Auto-incrementing with carryover
|
|
280
|
+
|===
|
|
281
|
+
|
|
282
|
+
[[declarative-schemes]]
|
|
283
|
+
=== Declarative custom schemes
|
|
284
|
+
|
|
285
|
+
[[declarative-schemes-general]]
|
|
286
|
+
==== General
|
|
287
|
+
|
|
288
|
+
Declarative schemes define version formats using segment definitions instead of
|
|
289
|
+
regular expressions. Each component specifies its type, separator, prefix, and
|
|
290
|
+
optional status, allowing the parser to extract components in O(n) time.
|
|
291
|
+
|
|
292
|
+
Benefits over regex-based patterns:
|
|
293
|
+
|
|
294
|
+
* **No ReDoS vulnerabilities** - State machine parsing is immune to catastrophic backtracking
|
|
295
|
+
* **O(n) performance** - Single pass through the string
|
|
296
|
+
* **Declarative syntax** - Clear, readable component definitions
|
|
297
|
+
* **Type-aware parsing** - Each component type handles its own validation
|
|
298
|
+
|
|
299
|
+
[[declarative-schemes-yaml]]
|
|
300
|
+
==== YAML schema
|
|
301
|
+
|
|
302
|
+
[source,yaml]
|
|
303
|
+
----
|
|
304
|
+
name: my_scheme <1>
|
|
305
|
+
type: declarative <2>
|
|
306
|
+
description: Custom scheme <3>
|
|
307
|
+
components: <4>
|
|
308
|
+
- name: major
|
|
309
|
+
type: integer
|
|
310
|
+
separator: "." <5>
|
|
311
|
+
- name: minor
|
|
312
|
+
type: integer
|
|
313
|
+
separator: "." <6>
|
|
314
|
+
- name: patch
|
|
315
|
+
type: integer <7>
|
|
316
|
+
----
|
|
317
|
+
<1> Unique scheme identifier (symbol).
|
|
318
|
+
<2> Scheme type: `declarative`.
|
|
319
|
+
<3> Optional description of the scheme.
|
|
320
|
+
<4> Array of component definitions in order.
|
|
321
|
+
<5> Separator that precedes this component.
|
|
322
|
+
<6> Each segment can have its own separator.
|
|
323
|
+
<7> Last segment typically has no separator.
|
|
324
|
+
|
|
325
|
+
Where,
|
|
326
|
+
|
|
327
|
+
`name`:: Symbol identifier for the scheme.
|
|
328
|
+
`type:: Scheme type (`declarative`).
|
|
329
|
+
`description:: Human-readable description.
|
|
330
|
+
`components:: Array of component definitions.
|
|
331
|
+
|
|
332
|
+
.Component attributes
|
|
333
|
+
|
|
334
|
+
[cols="1,1",options="header"]
|
|
335
|
+
|===
|
|
336
|
+
|Attribute |Purpose
|
|
337
|
+
|
|
338
|
+
|name
|
|
339
|
+
|Component name (symbol)
|
|
340
|
+
|
|
341
|
+
|type
|
|
342
|
+
|Component type (integer, string, enum, date_part, etc.)
|
|
343
|
+
|
|
344
|
+
|separator
|
|
345
|
+
|String that precedes this component (e.g., `"."`)
|
|
346
|
+
|
|
347
|
+
|prefix
|
|
348
|
+
|String that identifies an optional component (e.g., `"-"`, `"+"`)
|
|
349
|
+
|
|
350
|
+
|optional
|
|
351
|
+
|Boolean: true if component may be absent
|
|
352
|
+
|
|
353
|
+
|include_prefix_in_value
|
|
354
|
+
|Boolean: include prefix in value for component type parsing
|
|
355
|
+
|
|
356
|
+
|subtype
|
|
357
|
+
|For date_part: `year`, `month`, `day`, `week`
|
|
358
|
+
|
|
359
|
+
|values
|
|
360
|
+
|For enum type: allowed values
|
|
361
|
+
|
|
362
|
+
|order
|
|
363
|
+
|For enum type: comparison order
|
|
364
|
+
|
|
365
|
+
|validate
|
|
366
|
+
|Hash with `min`/`max` validation rules
|
|
367
|
+
|===
|
|
368
|
+
|
|
369
|
+
[[declarative-schemes-loading]]
|
|
370
|
+
==== Loading declarative schemes
|
|
371
|
+
|
|
372
|
+
[source,ruby]
|
|
373
|
+
----
|
|
374
|
+
# Load from YAML file
|
|
375
|
+
scheme = Versionian::SchemeLoader.from_yaml_file('schemes/custom.yaml')
|
|
376
|
+
|
|
377
|
+
# Register for use
|
|
378
|
+
Versionian.register_scheme(:custom, scheme)
|
|
379
|
+
|
|
380
|
+
# Use the scheme
|
|
381
|
+
version = scheme.parse("1.2.3")
|
|
382
|
+
puts version.major # => 1
|
|
383
|
+
----
|
|
384
|
+
|
|
385
|
+
[[declarative-schemes-examples]]
|
|
386
|
+
==== Declarative examples
|
|
387
|
+
|
|
388
|
+
[[semantic-declarative]]
|
|
389
|
+
===== Semantic versioning
|
|
390
|
+
|
|
391
|
+
[source,yaml]
|
|
392
|
+
----
|
|
393
|
+
name: semantic
|
|
394
|
+
type: declarative
|
|
395
|
+
description: Semantic versioning
|
|
396
|
+
components:
|
|
397
|
+
- name: major
|
|
398
|
+
type: integer
|
|
399
|
+
separator: "."
|
|
400
|
+
- name: minor
|
|
401
|
+
type: integer
|
|
402
|
+
separator: "."
|
|
403
|
+
- name: patch
|
|
404
|
+
type: integer
|
|
405
|
+
- name: prerelease
|
|
406
|
+
type: string
|
|
407
|
+
prefix: "-"
|
|
408
|
+
optional: true
|
|
409
|
+
- name: build
|
|
410
|
+
type: string
|
|
411
|
+
prefix: "+"
|
|
412
|
+
optional: true
|
|
413
|
+
----
|
|
414
|
+
|
|
415
|
+
Usage:
|
|
416
|
+
[source,ruby]
|
|
417
|
+
----
|
|
418
|
+
scheme = Versionian::SchemeLoader.from_yaml_file('schemes/semantic.yaml')
|
|
419
|
+
|
|
420
|
+
v = scheme.parse("1.2.3-alpha.1+build.123")
|
|
421
|
+
v.major # => 1
|
|
422
|
+
v.minor # => 2
|
|
423
|
+
v.patch # => 3
|
|
424
|
+
v.prerelease # => "alpha.1"
|
|
425
|
+
v.build # => "build.123"
|
|
426
|
+
|
|
427
|
+
# Optional components are nil when absent
|
|
428
|
+
v = scheme.parse("1.2.3")
|
|
429
|
+
v.prerelease # => nil
|
|
430
|
+
v.build # => nil
|
|
431
|
+
----
|
|
432
|
+
|
|
433
|
+
[[calver-declarative]]
|
|
434
|
+
===== Calendar versioning
|
|
435
|
+
|
|
436
|
+
[source,yaml]
|
|
437
|
+
----
|
|
438
|
+
name: calver
|
|
439
|
+
type: declarative
|
|
440
|
+
description: Calendar versioning YYYY.MM.DD
|
|
441
|
+
components:
|
|
442
|
+
- name: year
|
|
443
|
+
type: date_part
|
|
444
|
+
subtype: year
|
|
445
|
+
separator: "."
|
|
446
|
+
- name: month
|
|
447
|
+
type: date_part
|
|
448
|
+
subtype: month
|
|
449
|
+
separator: "."
|
|
450
|
+
- name: day
|
|
451
|
+
type: date_part
|
|
452
|
+
subtype: day
|
|
453
|
+
----
|
|
454
|
+
|
|
455
|
+
Usage:
|
|
456
|
+
[source,ruby]
|
|
457
|
+
----
|
|
458
|
+
scheme = Versionian::SchemeLoader.from_yaml_file('schemes/calver.yaml')
|
|
459
|
+
|
|
460
|
+
v = scheme.parse("2024.01.17")
|
|
461
|
+
v.year # => 2024
|
|
462
|
+
v.month # => 1 (not "01")
|
|
463
|
+
v.day # => 17
|
|
464
|
+
|
|
465
|
+
# Invalid dates raise ParseError
|
|
466
|
+
scheme.parse("2024.13.17") # => Invalid month '13'
|
|
467
|
+
scheme.parse("2024.02.30") # => Invalid day '30'
|
|
468
|
+
----
|
|
469
|
+
|
|
470
|
+
[[solover-declarative]]
|
|
471
|
+
===== SoloVer with postfix
|
|
472
|
+
|
|
473
|
+
[source,yaml]
|
|
474
|
+
----
|
|
475
|
+
name: solover
|
|
476
|
+
type: declarative
|
|
477
|
+
description: Single number with optional postfix
|
|
478
|
+
components:
|
|
479
|
+
- name: number
|
|
480
|
+
type: integer
|
|
481
|
+
- name: postfix
|
|
482
|
+
type: postfix
|
|
483
|
+
prefix: "+"
|
|
484
|
+
optional: true
|
|
485
|
+
include_prefix_in_value: true <1>
|
|
486
|
+
----
|
|
487
|
+
<1> Postfix type handles prefix internally, so include it in the value.
|
|
488
|
+
|
|
489
|
+
Usage:
|
|
490
|
+
[source,ruby]
|
|
491
|
+
----
|
|
492
|
+
scheme = Versionian::SchemeLoader.from_yaml_file('schemes/solover.yaml')
|
|
493
|
+
|
|
494
|
+
v = scheme.parse("5")
|
|
495
|
+
v.number # => 5
|
|
496
|
+
v.postfix # => nil
|
|
497
|
+
|
|
498
|
+
v = scheme.parse("5+hotfix")
|
|
499
|
+
v.number # => 5
|
|
500
|
+
v.postfix # => {:prefix=>"+", :identifier=>"hotfix"}
|
|
501
|
+
|
|
502
|
+
v = scheme.parse("5-beta")
|
|
503
|
+
v.number # => 5
|
|
504
|
+
v.postfix # => {:prefix=>"-", :identifier=>"beta"}
|
|
505
|
+
----
|
|
506
|
+
|
|
507
|
+
[[enum-declarative]]
|
|
508
|
+
===== Enum with custom ordering
|
|
509
|
+
|
|
510
|
+
[source,yaml]
|
|
511
|
+
----
|
|
512
|
+
name: stage
|
|
513
|
+
type: declarative
|
|
514
|
+
description: Release stage with ordering
|
|
515
|
+
components:
|
|
516
|
+
- name: major
|
|
517
|
+
type: integer
|
|
518
|
+
separator: "."
|
|
519
|
+
- name: minor
|
|
520
|
+
type: integer
|
|
521
|
+
separator: "."
|
|
522
|
+
- name: stage
|
|
523
|
+
type: enum
|
|
524
|
+
prefix: "-"
|
|
525
|
+
optional: true
|
|
526
|
+
values: [alpha, beta, rc, stable]
|
|
527
|
+
order: [alpha, beta, rc, stable]
|
|
528
|
+
----
|
|
529
|
+
|
|
530
|
+
Usage:
|
|
531
|
+
[source,ruby]
|
|
532
|
+
----
|
|
533
|
+
scheme = Versionian::SchemeLoader.from_yaml_file('schemes/stage.yaml')
|
|
534
|
+
|
|
535
|
+
v = scheme.parse("1.2.0-beta")
|
|
536
|
+
v.stage # => :beta
|
|
537
|
+
|
|
538
|
+
# Comparison respects order
|
|
539
|
+
scheme.compare("1.2.0-alpha", "1.2.0-beta") # => -1 (alpha < beta)
|
|
540
|
+
scheme.compare("1.2.0-rc", "1.2.0-stable") # => -1 (rc < stable)
|
|
541
|
+
----
|
|
542
|
+
|
|
543
|
+
[[discovering-schemes]]
|
|
544
|
+
=== Discovering available schemes
|
|
545
|
+
|
|
546
|
+
Versionian provides several ways to discover available schemes:
|
|
547
|
+
|
|
548
|
+
.List registered schemes
|
|
549
|
+
[source,ruby]
|
|
550
|
+
----
|
|
551
|
+
# List all registered scheme names
|
|
552
|
+
scheme_names = Versionian.scheme_registry.registered
|
|
553
|
+
puts scheme_names.inspect # => [:semantic, :calver, :solover, :wendtver]
|
|
554
|
+
|
|
555
|
+
# Check if a scheme exists
|
|
556
|
+
Versionian.scheme_registry.registered.include?(:semantic) # => true
|
|
557
|
+
----
|
|
558
|
+
|
|
559
|
+
.Detect scheme from version string
|
|
560
|
+
[source,ruby]
|
|
561
|
+
----
|
|
562
|
+
# Auto-detect which scheme matches a version string
|
|
563
|
+
detected = Versionian.detect_scheme("1.2.3")
|
|
564
|
+
puts detected.name # => :semantic
|
|
565
|
+
|
|
566
|
+
detected = Versionian.detect_scheme("2024.01.17")
|
|
567
|
+
puts detected.name # => :calver
|
|
568
|
+
|
|
569
|
+
detected = Versionian.detect_scheme("5+hotfix")
|
|
570
|
+
puts detected.name # => :solover
|
|
571
|
+
|
|
572
|
+
# Returns nil for unrecognised version strings
|
|
573
|
+
detected = Versionian.detect_scheme("not-a-version")
|
|
574
|
+
puts detected # => nil
|
|
575
|
+
----
|
|
576
|
+
|
|
577
|
+
.Get and use a scheme
|
|
578
|
+
[source,ruby]
|
|
579
|
+
----
|
|
580
|
+
# Get a scheme by name
|
|
581
|
+
scheme = Versionian.get_scheme(:semantic)
|
|
582
|
+
|
|
583
|
+
# Check scheme capabilities
|
|
584
|
+
puts scheme.supports?("1.2.3") # => true
|
|
585
|
+
puts scheme.supports?("invalid") # => false
|
|
586
|
+
|
|
587
|
+
# Parse and compare
|
|
588
|
+
version = scheme.parse("1.12.0")
|
|
589
|
+
puts version.to_s # => "1.12.0"
|
|
590
|
+
|
|
591
|
+
# Raises error for unknown schemes
|
|
592
|
+
begin
|
|
593
|
+
Versionian.get_scheme(:unknown)
|
|
594
|
+
rescue Versionian::Errors::InvalidSchemeError => e
|
|
595
|
+
puts e.message # => "Unknown scheme: unknown"
|
|
596
|
+
end
|
|
597
|
+
----
|
|
598
|
+
|
|
599
|
+
[[component-types]]
|
|
600
|
+
=== Component type system
|
|
601
|
+
|
|
602
|
+
[[component-types-general]]
|
|
603
|
+
==== General
|
|
604
|
+
|
|
605
|
+
Versionian provides built-in component types for parsing and comparing version
|
|
606
|
+
components. Each type defines its own parsing, comparison, and formatting behavior.
|
|
607
|
+
|
|
608
|
+
[[component-types-table]]
|
|
609
|
+
==== Available types
|
|
610
|
+
|
|
611
|
+
[cols="1,1,1",options="header"]
|
|
612
|
+
|===
|
|
613
|
+
|Type |Purpose |Comparison
|
|
614
|
+
|
|
615
|
+
|Integer
|
|
616
|
+
|Numeric sequences (major, minor, patch)
|
|
617
|
+
|Numeric
|
|
618
|
+
|
|
619
|
+
|Float
|
|
620
|
+
|FloatVer, IEEE754 floats
|
|
621
|
+
|Float comparison
|
|
622
|
+
|
|
623
|
+
|String
|
|
624
|
+
|Arbitrary text
|
|
625
|
+
|Lexicographic
|
|
626
|
+
|
|
627
|
+
|Enum
|
|
628
|
+
|Ordered stages (alpha < beta < rc)
|
|
629
|
+
|Order array index
|
|
630
|
+
|
|
631
|
+
|DatePart
|
|
632
|
+
|Calendar components (year, month, day, week)
|
|
633
|
+
|Numeric with validation
|
|
634
|
+
|
|
635
|
+
|Prerelease
|
|
636
|
+
|SemVer prereleases (alpha.1, beta.2)
|
|
637
|
+
|SemVer rules (numeric < alphanumeric)
|
|
638
|
+
|
|
639
|
+
|Postfix
|
|
640
|
+
|SoloVer suffixes (+hotfix, -beta)
|
|
641
|
+
|Prefix-sensitive (none < + < -)
|
|
642
|
+
|
|
643
|
+
|Hash
|
|
644
|
+
|Git commit hashes
|
|
645
|
+
|Length first, then lexicographic
|
|
646
|
+
|
|
647
|
+
|Wildcard
|
|
648
|
+
|Ignored components
|
|
649
|
+
|Always equal
|
|
650
|
+
|===
|
|
651
|
+
|
|
652
|
+
[[component-types-reference]]
|
|
653
|
+
==== Type reference
|
|
654
|
+
|
|
655
|
+
.Integer type
|
|
656
|
+
[source,yaml]
|
|
657
|
+
----
|
|
658
|
+
components:
|
|
659
|
+
- name: major
|
|
660
|
+
type: integer
|
|
661
|
+
----
|
|
662
|
+
|
|
663
|
+
.Float type
|
|
664
|
+
[source,yaml]
|
|
665
|
+
----
|
|
666
|
+
components:
|
|
667
|
+
- name: version
|
|
668
|
+
type: float
|
|
669
|
+
----
|
|
670
|
+
|
|
671
|
+
.String type
|
|
672
|
+
[source,yaml]
|
|
673
|
+
----
|
|
674
|
+
components:
|
|
675
|
+
- name: build
|
|
676
|
+
type: string
|
|
677
|
+
----
|
|
678
|
+
|
|
679
|
+
.Enum type with custom ordering
|
|
680
|
+
[source,yaml]
|
|
681
|
+
----
|
|
682
|
+
components:
|
|
683
|
+
- name: stage
|
|
684
|
+
type: enum
|
|
685
|
+
prefix: "-"
|
|
686
|
+
optional: true
|
|
687
|
+
values: [alpha, beta, rc, stable]
|
|
688
|
+
order: [alpha, beta, rc, stable]
|
|
689
|
+
----
|
|
690
|
+
|
|
691
|
+
.DatePart type with validation
|
|
692
|
+
[source,yaml]
|
|
693
|
+
----
|
|
694
|
+
components:
|
|
695
|
+
- name: year
|
|
696
|
+
type: date_part
|
|
697
|
+
subtype: year
|
|
698
|
+
separator: "."
|
|
699
|
+
- name: month
|
|
700
|
+
type: date_part
|
|
701
|
+
subtype: month
|
|
702
|
+
separator: "."
|
|
703
|
+
- name: day
|
|
704
|
+
type: date_part
|
|
705
|
+
subtype: day
|
|
706
|
+
----
|
|
707
|
+
|
|
708
|
+
.Prerelease type (SemVer)
|
|
709
|
+
[source,yaml]
|
|
710
|
+
----
|
|
711
|
+
components:
|
|
712
|
+
- name: prerelease
|
|
713
|
+
type: prerelease
|
|
714
|
+
prefix: "-"
|
|
715
|
+
optional: true
|
|
716
|
+
----
|
|
717
|
+
|
|
718
|
+
.Postfix type (SoloVer)
|
|
719
|
+
[source,yaml]
|
|
720
|
+
----
|
|
721
|
+
components:
|
|
722
|
+
- name: postfix
|
|
723
|
+
type: postfix
|
|
724
|
+
prefix: "+"
|
|
725
|
+
optional: true
|
|
726
|
+
include_prefix_in_value: true
|
|
727
|
+
----
|
|
728
|
+
|
|
729
|
+
.Hash type
|
|
730
|
+
[source,yaml]
|
|
731
|
+
----
|
|
732
|
+
components:
|
|
733
|
+
- name: hash
|
|
734
|
+
type: hash
|
|
735
|
+
----
|
|
736
|
+
|
|
737
|
+
[[range-matching]]
|
|
738
|
+
=== Version range matching
|
|
739
|
+
|
|
740
|
+
[[range-matching-general]]
|
|
741
|
+
==== General
|
|
742
|
+
|
|
743
|
+
Versionian supports matching versions against range specifications.
|
|
744
|
+
|
|
745
|
+
[[range-matching-types]]
|
|
746
|
+
==== Range types
|
|
747
|
+
|
|
748
|
+
* **equals**: Exact version match
|
|
749
|
+
* **before**: Less than boundary version
|
|
750
|
+
* **after**: Greater than or equal to boundary version
|
|
751
|
+
* **between**: Inclusive range between two versions
|
|
752
|
+
|
|
753
|
+
[[range-matching-usage]]
|
|
754
|
+
==== Usage
|
|
755
|
+
|
|
756
|
+
[source,ruby]
|
|
757
|
+
----
|
|
758
|
+
scheme = Versionian.get_scheme(:semantic)
|
|
759
|
+
|
|
760
|
+
# After a specific version (inclusive)
|
|
761
|
+
range = Versionian::VersionRange.new(:after, scheme, version: "1.12.0")
|
|
762
|
+
range.matches?("1.13.0") # => true
|
|
763
|
+
range.matches?("1.12.0") # => true
|
|
764
|
+
range.matches?("1.11.0") # => false
|
|
765
|
+
|
|
766
|
+
# Between two versions
|
|
767
|
+
range = Versionian::VersionRange.new(:between, scheme, from: "1.12.0", to: "2.0.0")
|
|
768
|
+
range.matches?("1.15.0") # => true
|
|
769
|
+
range.matches?("2.1.0") # => false
|
|
770
|
+
----
|
|
771
|
+
|
|
772
|
+
[[custom-schemes]]
|
|
773
|
+
=== Custom Ruby schemes
|
|
774
|
+
|
|
775
|
+
[[custom-schemes-general]]
|
|
776
|
+
==== General
|
|
777
|
+
|
|
778
|
+
For complex versioning schemes, you can create custom scheme classes in Ruby.
|
|
779
|
+
|
|
780
|
+
[[custom-schemes-example]]
|
|
781
|
+
==== Custom scheme example
|
|
782
|
+
|
|
783
|
+
[source,ruby]
|
|
784
|
+
----
|
|
785
|
+
class FloatVerScheme < Versionian::VersionScheme
|
|
786
|
+
def initialize(name: :floatver, description: "Float-based versioning")
|
|
787
|
+
super
|
|
788
|
+
end
|
|
789
|
+
|
|
790
|
+
def parse(version_string)
|
|
791
|
+
float_value = Float(version_string)
|
|
792
|
+
Versionian::VersionIdentifier.new(
|
|
793
|
+
raw_string: version_string,
|
|
794
|
+
scheme: self,
|
|
795
|
+
components: [],
|
|
796
|
+
comparable_array: [float_value]
|
|
797
|
+
)
|
|
798
|
+
end
|
|
799
|
+
|
|
800
|
+
def compare_arrays(a, b)
|
|
801
|
+
a.first <=> b.first # Direct float comparison
|
|
802
|
+
end
|
|
803
|
+
|
|
804
|
+
def render(version)
|
|
805
|
+
version.raw_string
|
|
806
|
+
end
|
|
807
|
+
end
|
|
808
|
+
|
|
809
|
+
# Register and use
|
|
810
|
+
Versionian.register_scheme(:floatver, FloatVerScheme.new)
|
|
811
|
+
----
|
|
812
|
+
|
|
813
|
+
[[yaml-config]]
|
|
814
|
+
=== YAML configuration
|
|
815
|
+
|
|
816
|
+
[[yaml-config-safety]]
|
|
817
|
+
==== Safe loading
|
|
818
|
+
|
|
819
|
+
Versionian uses `Psych.safe_load` with a restricted set of permitted classes for
|
|
820
|
+
security:
|
|
821
|
+
|
|
822
|
+
[source,ruby]
|
|
823
|
+
----
|
|
824
|
+
ALLOWED_CLASSES = [Symbol, Integer, String, Array, Hash, TrueClass, FalseClass, NilClass].freeze
|
|
825
|
+
----
|
|
826
|
+
|
|
827
|
+
[[yaml-config-loading]]
|
|
828
|
+
==== Loading schemes
|
|
829
|
+
|
|
830
|
+
[source,ruby]
|
|
831
|
+
----
|
|
832
|
+
# From file
|
|
833
|
+
scheme = Versionian::SchemeLoader.from_yaml_file('schemes/custom.yaml')
|
|
834
|
+
|
|
835
|
+
# From string
|
|
836
|
+
yaml_string = <<~YAML
|
|
837
|
+
name: custom
|
|
838
|
+
type: declarative
|
|
839
|
+
components:
|
|
840
|
+
- { name: major, type: integer, separator: "." }
|
|
841
|
+
- { name: minor, type: integer }
|
|
842
|
+
YAML
|
|
843
|
+
|
|
844
|
+
scheme = Versionian::SchemeLoader.from_yaml_string(yaml_string)
|
|
845
|
+
----
|
|
846
|
+
|
|
847
|
+
== Development
|
|
848
|
+
|
|
849
|
+
After checking out the repo, run:
|
|
850
|
+
|
|
851
|
+
[source,shell]
|
|
852
|
+
----
|
|
853
|
+
bundle install
|
|
854
|
+
----
|
|
855
|
+
|
|
856
|
+
Run tests:
|
|
857
|
+
|
|
858
|
+
[source,shell]
|
|
859
|
+
----
|
|
860
|
+
bundle exec rspec
|
|
861
|
+
----
|
|
862
|
+
|
|
863
|
+
Run linting:
|
|
864
|
+
|
|
865
|
+
[source,shell]
|
|
866
|
+
----
|
|
867
|
+
bundle exec rubocop
|
|
868
|
+
----
|
|
869
|
+
|
|
870
|
+
== Copyright and license
|
|
871
|
+
|
|
872
|
+
Copyright Ribose.
|
|
873
|
+
|
|
874
|
+
MIT License - see LICENSE file for details.
|